Merge 665b820320 into a5ecbb8b1e
This commit is contained in:
commit
5960f7d461
|
|
@ -0,0 +1,811 @@
|
|||
# from Anesidora
|
||||
from . import DatabaseObject
|
||||
from direct.showbase import DirectObject
|
||||
from direct.showbase.PythonUtil import intersection
|
||||
from toontown.toon import DistributedToonAI
|
||||
from toontown.estate import DistributedHouseAI
|
||||
from toontown.pets import DistributedPetAI
|
||||
from toontown.toon import InventoryBase
|
||||
from pandac.PandaModules import *
|
||||
from toontown.quest import Quests
|
||||
from toontown.toon import NPCToons
|
||||
import time
|
||||
from functools import reduce
|
||||
|
||||
HEAL_TRACK = 0
|
||||
TRAP_TRACK = 1
|
||||
LURE_TRACK = 2
|
||||
SOUND_TRACK = 3
|
||||
THROW_TRACK = 4
|
||||
SQUIRT_TRACK = 5
|
||||
DROP_TRACK = 6
|
||||
|
||||
class AvatarGetter(DirectObject.DirectObject):
|
||||
# Gets just one avatar at a time. You can munge properties on the
|
||||
# avatar and write it back to the database. self.av is the
|
||||
# avatar.
|
||||
|
||||
# An incrementing sequence number unique to each getter object.
|
||||
nextSequence = 1
|
||||
|
||||
def __init__(self, air):
|
||||
self.air = air
|
||||
self.dclass = self.air.dclassesByName['DistributedToonAI']
|
||||
self.av = None
|
||||
self.gotAvatarEvent = 'AvatarGetter-%s' % (self.nextSequence)
|
||||
AvatarGetter.nextSequence += 1
|
||||
|
||||
def getAvatar(self, avId, fields=None, event=None):
|
||||
# Requests a particular avatar. The avatar will be requested
|
||||
# from the database and stored in self.av when the response is
|
||||
# heard back from the database, at some time in the future.
|
||||
self.av = None
|
||||
self.event = event
|
||||
|
||||
self.acceptOnce(self.gotAvatarEvent, self.__gotData)
|
||||
|
||||
db = DatabaseObject.DatabaseObject(self.air, avId)
|
||||
db.doneEvent = self.gotAvatarEvent
|
||||
if fields is None:
|
||||
fields = db.getDatabaseFields(self.dclass)
|
||||
elif 'setDNAString' not in fields:
|
||||
# we need this to check if it's a Toon
|
||||
fields.append('setDNAString')
|
||||
db.getFields(fields)
|
||||
print("Avatar %s requested." % avId)
|
||||
|
||||
def saveAvatarAll(self):
|
||||
# Writes all the fields on the current avatar back to the
|
||||
# database.
|
||||
db = DatabaseObject.DatabaseObject(self.air, self.av.doId)
|
||||
db.storeObject(self.av)
|
||||
print("Saved avatar %s." % (self.av.doId))
|
||||
|
||||
def saveAvatar(self, *fields):
|
||||
# Writes only the named fields (strings passed as parameters)
|
||||
# on the current avatar back to the database.
|
||||
if (len(fields) == 0):
|
||||
print("Specify the fields to save in the parameter list, or use saveAvatarAll().")
|
||||
else:
|
||||
db = DatabaseObject.DatabaseObject(self.air, self.av.doId)
|
||||
db.storeObject(self.av, fields)
|
||||
print("Saved %d fields on avatar %s." % (len(fields), self.av.doId))
|
||||
|
||||
def __gotData(self, db, retcode):
|
||||
if retcode == 0 and 'setDNAString' in db.values:
|
||||
self.av = DistributedToonAI.DistributedToonAI(self.air)
|
||||
self.av.doId = db.doId
|
||||
self.av.inventory = InventoryBase.InventoryBase(self.av)
|
||||
self.av.teleportZoneArray = []
|
||||
db.fillin(self.av, self.dclass)
|
||||
|
||||
# to prevent mem leaks, you should call toon.patchDelete at
|
||||
# some point.
|
||||
|
||||
print('Got avatar %s, "%s".' % (self.av.doId, self.av._name))
|
||||
if self.event is not None:
|
||||
messenger.send(self.event, [self.av])
|
||||
else:
|
||||
print("Could not get avatar %s, retcode = %s." % (db.doId, retcode))
|
||||
if self.event is not None:
|
||||
messenger.send(self.event, [None])
|
||||
|
||||
class AvatarIterator(DirectObject.DirectObject):
|
||||
|
||||
# The maximum number of outstanding requests to make to the server
|
||||
# at once.
|
||||
maxRequests = 20
|
||||
|
||||
# When we come to this many non-avatars in a row, assume we have
|
||||
# reached the end of the database.
|
||||
endOfListCount = 20
|
||||
|
||||
# The amount of time, in seconds, to elapse between displaying
|
||||
# successive avatars.
|
||||
printInterval = 1.0
|
||||
|
||||
# An incrementing sequence number unique to each iterator object.
|
||||
nextSequence = 1
|
||||
|
||||
def __init__(self, air):
|
||||
self.air = air
|
||||
self.dclass = self.air.dclassesByName['DistributedToonAI']
|
||||
self.dnaDict = {}
|
||||
self.nextObjId = None
|
||||
self.objIdList = None # Fill this with a list of objId's to iterate through the list.
|
||||
self.requested = []
|
||||
self.nonAvatar = 0
|
||||
self.gotAvatarEvent = 'AvatarIterator-%s' % (self.nextSequence)
|
||||
AvatarIterator.nextSequence += 1
|
||||
|
||||
def start(self, startId = 100000000):
|
||||
self.startTime = time.time()
|
||||
if self.objIdList != None:
|
||||
# Iterate through an explicit list
|
||||
self.nextObjId = None
|
||||
self.objIdIndex = 0
|
||||
else:
|
||||
# Iterate through the whole database
|
||||
self.nextObjId = startId
|
||||
self.accept(self.gotAvatarEvent, self.__gotData)
|
||||
self.requested = []
|
||||
self.lastPrintTime = 0
|
||||
self.getNextAvatar()
|
||||
|
||||
def stop(self):
|
||||
self.ignoreAll()
|
||||
|
||||
def getNextAvatar(self):
|
||||
while len(self.requested) < self.maxRequests:
|
||||
if self.nextObjId != None:
|
||||
db = DatabaseObject.DatabaseObject(self.air, self.nextObjId)
|
||||
db.doneEvent = self.gotAvatarEvent
|
||||
db.getFields(self.fieldsToGet(db))
|
||||
self.requested.append(self.nextObjId)
|
||||
|
||||
if self.objIdList != None:
|
||||
# Iterate through an explicit list
|
||||
if self.objIdIndex >= len(self.objIdList):
|
||||
# Done.
|
||||
self.nextObjId = None
|
||||
if len(self.requested) == 0:
|
||||
self.done()
|
||||
return
|
||||
|
||||
self.nextObjId = int(self.objIdList[self.objIdIndex])
|
||||
self.objIdIndex += 1
|
||||
|
||||
else:
|
||||
# Iterate through the whole database
|
||||
self.nextObjId += 2
|
||||
|
||||
def fieldsToGet(self, db):
|
||||
return db.getDatabaseFields(self.dclass)
|
||||
|
||||
def __gotData(self, db, retcode):
|
||||
self.requested.remove(db.doId)
|
||||
if retcode == 0 and 'setMoney' in db.values:
|
||||
av = DistributedToonAI.DistributedToonAI(self.air)
|
||||
av.doId = db.doId
|
||||
av.inventory = InventoryBase.InventoryBase(av)
|
||||
av.teleportZoneArray = []
|
||||
db.fillin(av, self.dclass)
|
||||
self.processAvatar(av, db)
|
||||
self.nonAvatar = 0
|
||||
else:
|
||||
if self.objIdList != None:
|
||||
print("Not an avatar: %s" % (db.doId))
|
||||
self.nonAvatar += 1
|
||||
|
||||
if self.objIdList != None or self.nonAvatar < self.endOfListCount:
|
||||
self.getNextAvatar()
|
||||
elif len(self.requested) == 0:
|
||||
self.stop()
|
||||
self.done()
|
||||
|
||||
def printSometimes(self, av):
|
||||
now = time.time()
|
||||
if now - self.lastPrintTime > self.printInterval:
|
||||
print("%d: %s" % (av.doId, av._name))
|
||||
self.lastPrintTime = now
|
||||
|
||||
def processAvatar(self, av, db):
|
||||
self.printSometimes(av)
|
||||
|
||||
def done(self):
|
||||
now = time.time()
|
||||
print("done, %s seconds." % (now - self.startTime))
|
||||
|
||||
|
||||
class HouseIterator(DirectObject.DirectObject):
|
||||
|
||||
# The maximum number of outstanding requests to make to the server
|
||||
# at once.
|
||||
maxRequests = 20
|
||||
|
||||
# When we come to this many non-houses in a row, assume we have
|
||||
# reached the end of the database.
|
||||
endOfListCount = 20
|
||||
|
||||
# The amount of time, in seconds, to elapse between displaying
|
||||
# successive houses.
|
||||
printInterval = 1.0
|
||||
|
||||
# An incrementing sequence number unique to each iterator object.
|
||||
nextSequence = 1
|
||||
|
||||
def __init__(self, air):
|
||||
self.air = air
|
||||
self.dclass = self.air.dclassesByName['DistributedHouseAI']
|
||||
self.dnaDict = {}
|
||||
self.nextObjId = None
|
||||
self.objIdList = None # Fill this with a list of objId's to iterate through the list.
|
||||
self.requested = []
|
||||
self.nonHouse = 0
|
||||
self.gotHouseEvent = 'HouseIterator-%s' % (self.nextSequence)
|
||||
HouseIterator.nextSequence += 1
|
||||
|
||||
def start(self, startId = 100000000):
|
||||
self.startTime = time.time()
|
||||
if self.objIdList != None:
|
||||
# Iterate through an explicit list
|
||||
self.nextObjId = None
|
||||
self.objIdIndex = 0
|
||||
else:
|
||||
# Iterate through the whole database
|
||||
self.nextObjId = startId
|
||||
self.accept(self.gotHouseEvent, self.__gotData)
|
||||
self.requested = []
|
||||
self.lastPrintTime = 0
|
||||
self.getNextHouse()
|
||||
|
||||
def stop(self):
|
||||
self.ignoreAll()
|
||||
|
||||
def getNextHouse(self):
|
||||
while len(self.requested) < self.maxRequests:
|
||||
if self.nextObjId != None:
|
||||
db = DatabaseObject.DatabaseObject(self.air, self.nextObjId)
|
||||
db.doneEvent = self.gotHouseEvent
|
||||
db.getFields(self.fieldsToGet(db))
|
||||
self.requested.append(self.nextObjId)
|
||||
|
||||
if self.objIdList != None:
|
||||
# Iterate through an explicit list
|
||||
if self.objIdIndex >= len(self.objIdList):
|
||||
# Done.
|
||||
self.nextObjId = None
|
||||
if len(self.requested) == 0:
|
||||
self.done()
|
||||
return
|
||||
|
||||
self.nextObjId = int(self.objIdList[self.objIdIndex])
|
||||
self.objIdIndex += 1
|
||||
|
||||
else:
|
||||
# Iterate through the whole database
|
||||
self.nextObjId += 2
|
||||
|
||||
def fieldsToGet(self, db):
|
||||
return db.getDatabaseFields(self.dclass)
|
||||
|
||||
def __gotData(self, db, retcode):
|
||||
self.requested.remove(db.doId)
|
||||
if retcode == 0 and ('setHouseType' in db.values or
|
||||
'setInteriorWallpaper' in db.values):
|
||||
# Fill in dummy values of estateId, zoneId, and posIndex
|
||||
house = DistributedHouseAI.DistributedHouseAI(
|
||||
self.air, db.doId, 0, 0, 0)
|
||||
db.fillin(house, self.dclass)
|
||||
self.processHouse(house, db)
|
||||
self.nonHouse = 0
|
||||
else:
|
||||
if self.objIdList != None:
|
||||
print("Not a house: %s" % (db.doId))
|
||||
self.nonHouse += 1
|
||||
|
||||
if self.objIdList != None or self.nonHouse < self.endOfListCount:
|
||||
self.getNextHouse()
|
||||
elif len(self.requested) == 0:
|
||||
self.stop()
|
||||
self.done()
|
||||
|
||||
def printSometimes(self, house):
|
||||
now = time.time()
|
||||
if now - self.lastPrintTime > self.printInterval:
|
||||
print("%d: %s" % (house.doId, house.name))
|
||||
self.lastPrintTime = now
|
||||
|
||||
def processHouse(self, house, db):
|
||||
self.printSometimes(house)
|
||||
|
||||
def done(self):
|
||||
now = time.time()
|
||||
print("done, %s seconds." % (now - self.startTime))
|
||||
|
||||
|
||||
class PetIterator(DirectObject.DirectObject):
|
||||
|
||||
# The maximum number of outstanding requests to make to the server
|
||||
# at once.
|
||||
maxRequests = 10
|
||||
|
||||
# When we come to this many non-pets in a row, assume we have
|
||||
# reached the end of the database.
|
||||
endOfListCount = 20
|
||||
|
||||
# The amount of time, in seconds, to elapse between displaying
|
||||
# successive pets.
|
||||
printInterval = 1.0
|
||||
|
||||
# An incrementing sequence number unique to each iterator object.
|
||||
nextSequence = 1
|
||||
|
||||
def __init__(self, air):
|
||||
self.air = air
|
||||
self.dclass = self.air.dclassesByName['DistributedPetAI']
|
||||
self.dnaDict = {}
|
||||
self.startId = None
|
||||
self.endId = None
|
||||
self.nextObjId = None
|
||||
self.objIdList = None # Fill this with a list of objId's to iterate through the list.
|
||||
self.requested = []
|
||||
self.nonPet = 0
|
||||
self.gotPetEvent = 'PetIterator-%s' % (self.nextSequence)
|
||||
PetIterator.nextSequence += 1
|
||||
|
||||
def start(self, startId = 100000000):
|
||||
self.startTime = time.time()
|
||||
if self.objIdList != None:
|
||||
# Iterate through an explicit list
|
||||
self.nextObjId = None
|
||||
self.objIdIndex = 0
|
||||
else:
|
||||
# Iterate through the whole database
|
||||
if self.startId == None:
|
||||
self.startId = startId
|
||||
self.nextObjId = self.startId
|
||||
self.accept(self.gotPetEvent, self.__gotData)
|
||||
self.requested = []
|
||||
self.lastPrintTime = 0
|
||||
self.getNextPet()
|
||||
|
||||
def timeToStop(self):
|
||||
# override this and return True when appropriate
|
||||
if self.objIdList != None:
|
||||
return False
|
||||
return self.nonPet >= self.endOfListCount
|
||||
|
||||
def stop(self):
|
||||
self.ignoreAll()
|
||||
|
||||
def getNextPet(self):
|
||||
if self.timeToStop():
|
||||
return
|
||||
while len(self.requested) < self.maxRequests:
|
||||
if self.nextObjId != None:
|
||||
db = DatabaseObject.DatabaseObject(self.air, self.nextObjId)
|
||||
db.doneEvent = self.gotPetEvent
|
||||
db.getFields(self.fieldsToGet(db))
|
||||
self.requested.append(self.nextObjId)
|
||||
|
||||
if self.objIdList != None:
|
||||
# Iterate through an explicit list
|
||||
if self.objIdIndex >= len(self.objIdList):
|
||||
# Done.
|
||||
self.nextObjId = None
|
||||
if len(self.requested) == 0:
|
||||
self.done()
|
||||
return
|
||||
|
||||
self.nextObjId = int(self.objIdList[self.objIdIndex])
|
||||
self.objIdIndex += 1
|
||||
|
||||
else:
|
||||
# Iterate through the whole database
|
||||
self.nextObjId += 2
|
||||
|
||||
def fieldsToGet(self, db):
|
||||
return db.getDatabaseFields(self.dclass)
|
||||
|
||||
def __gotData(self, db, retcode):
|
||||
self.requested.remove(db.doId)
|
||||
if retcode == 0 and len(intersection(list(db.values.keys()),
|
||||
self.fieldsToGet(None))) > 0:
|
||||
pet = DistributedPetAI.DistributedPetAI(self.air)
|
||||
db.fillin(pet, self.dclass)
|
||||
self.processPet(pet, db)
|
||||
self.nonPet = 0
|
||||
else:
|
||||
if self.objIdList != None:
|
||||
print("Not a pet: %s" % (db.doId))
|
||||
self.nonPet += 1
|
||||
self.getNextPet()
|
||||
|
||||
if self.timeToStop() and (len(self.requested) == 0):
|
||||
self.stop()
|
||||
self.done()
|
||||
|
||||
def printSometimes(self, pet):
|
||||
now = time.time()
|
||||
if now - self.lastPrintTime > self.printInterval:
|
||||
percent = None
|
||||
if self.objIdList != None:
|
||||
percent = 100. * self.objIdIndex / len(self.objIdList)
|
||||
elif self.endId is not None:
|
||||
percent = 100. * ((self.nextObjId - self.startId) /
|
||||
(self.endId - self.startId))
|
||||
if percent is not None:
|
||||
print("%s%% complete, %s seconds" % (percent,
|
||||
(now - self.startTime)))
|
||||
else:
|
||||
print("%d: %s, %s seconds" % (pet.doId, pet.petName,
|
||||
(now - self.startTime)))
|
||||
self.lastPrintTime = now
|
||||
|
||||
def processPet(self, pet, db):
|
||||
self.printSometimes(pet)
|
||||
|
||||
def done(self):
|
||||
now = time.time()
|
||||
print("done, %s seconds." % (now - self.startTime))
|
||||
|
||||
|
||||
class AvatarFixer(AvatarIterator):
|
||||
def processAvatar(self, av, db):
|
||||
self.printSometimes(av)
|
||||
|
||||
changed = av.fixAvatar()
|
||||
if changed:
|
||||
db2 = DatabaseObject.DatabaseObject(self.air, av.doId)
|
||||
db2.storeObject(av, list(db.values.keys()))
|
||||
print("%d: %s repaired (account %s)." % (av.doId, av._name, av.accountName))
|
||||
return
|
||||
|
||||
numTracks = reduce(lambda a, b: a+b, av.trackArray)
|
||||
hp = av.maxHp
|
||||
healExp, trapExp, lureExp, soundExp, throwExp, squirtExp, dropExp = av.experience.experience
|
||||
trackProgressId, trackProgress = av.getTrackProgress()
|
||||
trackAccess = av.getTrackAccess()
|
||||
maxMoney = av.getMaxMoney()
|
||||
fixed = 0
|
||||
|
||||
for questDesc in av.quests:
|
||||
|
||||
questId = questDesc[0]
|
||||
rewardId = questDesc[3]
|
||||
toNpc = questDesc[2]
|
||||
|
||||
if (not Quests.questExists(questId)):
|
||||
print('WARNING: av has quest that is not in quest dict: ', av.doId, questId)
|
||||
continue
|
||||
|
||||
if (questId in [160, 161, 162, 161]):
|
||||
if rewardId != 100:
|
||||
print(('WARNING: av has quest: %s with reward: %s' % (questId, rewardId)))
|
||||
questDesc[3] = 100
|
||||
fixed = 1
|
||||
continue
|
||||
|
||||
if (rewardId == 1000):
|
||||
if (questId in [1100, 1101, 1102, 1103, 2500, 2501, 3500, 3501, 4500, 4501, 5500, 5501, 7500, 7501, 9500, 9501]):
|
||||
# not fixing because this clothing quest is valid
|
||||
break
|
||||
av.removeAllTracesOfQuest(questId, rewardId)
|
||||
fixed = 1
|
||||
continue
|
||||
|
||||
if ((toNpc != 1000) and
|
||||
(NPCToons.NPCToonDict[toNpc][5] == NPCToons.NPC_HQ)):
|
||||
print(('WARNING: av has quest: %s to visit NPC_HQ: %s' % (questId, toNpc)))
|
||||
print('before: ', av.quests)
|
||||
questDesc[2] = Quests.ToonHQ
|
||||
print('after: ', av.quests)
|
||||
fixed = 1
|
||||
continue
|
||||
|
||||
# If there were any quest fixes, broadcast them now
|
||||
if fixed:
|
||||
av.b_setQuests(av.quests)
|
||||
|
||||
# Make sure they are not training any tracks they have already trained
|
||||
if (trackProgressId >= 0) and (trackAccess[trackProgressId] == 1):
|
||||
print ("WARNING: av training track he already has")
|
||||
print("Track progress id: ", trackProgressId)
|
||||
print("Track access: ", trackAccess)
|
||||
print("Tier: ", av.rewardTier)
|
||||
if av.rewardTier in [0, 1]:
|
||||
print("ERROR: You should not be here")
|
||||
elif av.rewardTier in [2, 3]:
|
||||
print('sound or heal')
|
||||
if av.trackArray[SOUND_TRACK] and not av.trackArray[HEAL_TRACK]:
|
||||
trackProgressId = HEAL_TRACK
|
||||
elif av.trackArray[HEAL_TRACK] and not av.trackArray[SOUND_TRACK]:
|
||||
trackProgressId = SOUND_TRACK
|
||||
else:
|
||||
trackProgressId = HEAL_TRACK
|
||||
av.b_setTrackProgress(trackProgressId, trackProgress)
|
||||
print("Fixed trackProgressId: ", trackProgressId)
|
||||
fixed = 1
|
||||
|
||||
elif av.rewardTier in [4]:
|
||||
print("ERROR: You should not be here")
|
||||
elif av.rewardTier in [5, 6]:
|
||||
print('drop or lure')
|
||||
if av.trackArray[DROP_TRACK] and not av.trackArray[LURE_TRACK]:
|
||||
trackProgressId = LURE_TRACK
|
||||
elif av.trackArray[LURE_TRACK] and not av.trackArray[DROP_TRACK]:
|
||||
trackProgressId = DROP_TRACK
|
||||
else:
|
||||
trackProgressId = DROP_TRACK
|
||||
av.b_setTrackProgress(trackProgressId, trackProgress)
|
||||
print("Fixed trackProgressId: ", trackProgressId)
|
||||
fixed = 1
|
||||
elif av.rewardTier in [7]:
|
||||
print("ERROR: You should not be here")
|
||||
elif av.rewardTier in [8]:
|
||||
print("ERROR: You should not be here")
|
||||
elif av.rewardTier in [9, 10]:
|
||||
print('trap or heal, trap or sound')
|
||||
if av.trackArray[SOUND_TRACK] and not av.trackArray[HEAL_TRACK]:
|
||||
trackProgressId = HEAL_TRACK
|
||||
elif av.trackArray[HEAL_TRACK] and not av.trackArray[SOUND_TRACK]:
|
||||
trackProgressId = SOUND_TRACK
|
||||
else:
|
||||
trackProgressId = TRAP_TRACK
|
||||
av.b_setTrackProgress(trackProgressId, trackProgress)
|
||||
print("Fixed trackProgressId: ", trackProgressId)
|
||||
fixed = 1
|
||||
elif av.rewardTier in [11]:
|
||||
print("ERROR: You should not be here")
|
||||
elif av.rewardTier in [12, 13]:
|
||||
print('all sort of choices')
|
||||
if not av.trackArray[HEAL_TRACK]:
|
||||
trackProgressId = HEAL_TRACK
|
||||
elif not av.trackArray[SOUND_TRACK]:
|
||||
trackProgressId = SOUND_TRACK
|
||||
elif not av.trackArray[DROP_TRACK]:
|
||||
trackProgressId = DROP_TRACK
|
||||
elif not av.trackArray[LURE_TRACK]:
|
||||
trackProgressId = LURE_TRACK
|
||||
elif not av.trackArray[TRAP_TRACK]:
|
||||
trackProgressId = TRAP_TRACK
|
||||
else:
|
||||
print("ERROR")
|
||||
av.b_setTrackProgress(trackProgressId, trackProgress)
|
||||
print("Fixed trackProgressId: ", trackProgressId)
|
||||
fixed = 1
|
||||
else:
|
||||
print("ERROR: You should not be here")
|
||||
print()
|
||||
|
||||
# clean up track access
|
||||
if av.fixTrackAccess():
|
||||
fixed = 1
|
||||
|
||||
# This was an unfortunate typo in Quests.py
|
||||
if maxMoney == 10:
|
||||
print('bad maxMoney limit == 10')
|
||||
av.b_setMaxMoney(100)
|
||||
# Fill er up cause we feel bad
|
||||
av.b_setMoney(100)
|
||||
fixed = 1
|
||||
|
||||
if av.rewardTier == 5:
|
||||
if hp < 25 or hp > 34:
|
||||
print('bad hp: ', end=' ')
|
||||
|
||||
# Somehow they got here without choosing a track
|
||||
if trackProgressId == -1:
|
||||
print('bad track training in tier 5!')
|
||||
print(('avId: %s, trackProgressId: %s, trackProgress: %s' %
|
||||
(av.doId, trackProgressId, trackProgress)))
|
||||
av.b_setQuestHistory([])
|
||||
av.b_setQuests([])
|
||||
# Make them choose again
|
||||
av.b_setRewardHistory(4, [])
|
||||
av.b_setTrackProgress(-1, 0)
|
||||
av.fixAvatar()
|
||||
av.inventory.zeroInv()
|
||||
av.inventory.maxOutInv()
|
||||
av.d_setInventory(av.inventory.makeNetString())
|
||||
print('new track access: ', av.trackArray)
|
||||
fixed = 1
|
||||
|
||||
elif av.rewardTier == 7:
|
||||
if hp < 34 or hp > 43:
|
||||
print('bad hp: ', end=' ')
|
||||
if trackProgressId != -1:
|
||||
print('bad track training in tier 7!')
|
||||
av.b_setQuestHistory([])
|
||||
av.b_setQuests([])
|
||||
av.b_setRewardHistory(7, [])
|
||||
av.b_setTrackProgress(-1, 0)
|
||||
av.fixAvatar()
|
||||
av.inventory.zeroInv()
|
||||
av.inventory.maxOutInv()
|
||||
av.d_setInventory(av.inventory.makeNetString())
|
||||
fixed = 1
|
||||
|
||||
else:
|
||||
# Nothing to fix here
|
||||
pass
|
||||
|
||||
if fixed:
|
||||
db = DatabaseObject.DatabaseObject(self.air, av.doId)
|
||||
db.storeObject(av)
|
||||
print("Avatar repaired.")
|
||||
print()
|
||||
|
||||
return
|
||||
|
||||
class AvatarPrinter(AvatarIterator):
|
||||
|
||||
def __init__(self, air):
|
||||
AvatarIterator.__init__(self, air)
|
||||
self.hoodInfoStore = HoodInfoStore()
|
||||
|
||||
def processAvatar(self, av, db):
|
||||
numFriends = 0
|
||||
numSecretFriends = 0
|
||||
cogCount = 0
|
||||
|
||||
for friend in av.friendsList:
|
||||
numFriends += 1
|
||||
if friend[1]:
|
||||
numSecretFriends += 1
|
||||
|
||||
for cogTotal in av.cogCounts:
|
||||
cogCount += cogTotal
|
||||
|
||||
#if ((av.maxHp == 15) and
|
||||
# (av.experience.experience[0] +
|
||||
# av.experience.experience[1] +
|
||||
# av.experience.experience[2] +
|
||||
# av.experience.experience[3] +
|
||||
# av.experience.experience[4] +
|
||||
# av.experience.experience[5] +
|
||||
# av.experience.experience[6] == 0) and
|
||||
# numFriends == 0):
|
||||
# # Do not count this fella
|
||||
# return
|
||||
|
||||
|
||||
#oldDna = self.backupDict.get(av.doId)
|
||||
#newDna = av.dna.asTuple()
|
||||
#if oldDna and (oldDna != newDna):
|
||||
# print '================'
|
||||
# print av.doId
|
||||
# print oldDna
|
||||
# print newDna
|
||||
|
||||
#self.dnaDict[av.doId] = av.dna.asTuple()
|
||||
#print av.doId, ' finished' #, av._name, "dna: ", av.dna.asTuple()
|
||||
#return
|
||||
#import ToonDNA
|
||||
#newDNA = ToonDNA.ToonDNA()
|
||||
#newDNA.newToonFromProperties(*av.dna.asTuple())
|
||||
#print 'old: ', av.dna
|
||||
#print 'new: ', newDNA
|
||||
|
||||
if av.doId % 10000 == 0:
|
||||
print(("Working on avatar: %s" % av.doId))
|
||||
#print ("%s, %s, %s, %s, %s" %
|
||||
# (av.doId, av.maxHp, len(av.hoodsVisited), len(av.safeZonesVisited), cogCount))
|
||||
self.hoodInfoStore.record(av.maxHp, len(av.hoodsVisited), len(av.safeZonesVisited))
|
||||
return
|
||||
|
||||
print(("%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s" %
|
||||
(av.doId,
|
||||
av._name,
|
||||
av.maxHp,
|
||||
av.dna.head,
|
||||
av.dna.topTex,
|
||||
av.dna.botTex,
|
||||
av.dna.armColor,
|
||||
av.dna.legColor,
|
||||
av.dna.headColor,
|
||||
numFriends,
|
||||
numSecretFriends,
|
||||
len(av.hoodsVisited),
|
||||
av.rewardTier,
|
||||
av.trackArray,
|
||||
av.trackProgressId,
|
||||
av.trackProgress,
|
||||
# Experience is a tuple of 6 values
|
||||
av.experience.experience[0],
|
||||
av.experience.experience[1],
|
||||
av.experience.experience[2],
|
||||
av.experience.experience[3],
|
||||
av.experience.experience[4],
|
||||
av.experience.experience[5],
|
||||
av.experience.experience[6],
|
||||
)))
|
||||
|
||||
|
||||
|
||||
class HoodInfoStore:
|
||||
def __init__(self):
|
||||
self.__printCount = 0
|
||||
# Init the values to 0
|
||||
self.avatarCount = {15:0,
|
||||
16:0,
|
||||
17:0,
|
||||
18:0,
|
||||
19:0,
|
||||
20:0,
|
||||
}
|
||||
self.hoodsVisited = {15: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
16: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
17: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
18: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
19: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
20: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
}
|
||||
self.safeZonesVisited = {15: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
16: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
17: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
18: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
19: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
20: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0},
|
||||
}
|
||||
|
||||
def maybePrint(self):
|
||||
self.__printCount += 1
|
||||
if self.__printCount % 100 == 0:
|
||||
for hp, count in list(self.avatarCount.items()):
|
||||
if count > 0:
|
||||
print(("hp: %d count: %d hoods: %s sz: %s" %
|
||||
(hp, count, self.hoodsVisited[hp], self.safeZonesVisited[hp])))
|
||||
print()
|
||||
|
||||
def record(self, hp, numHoods, numSafeZones):
|
||||
self.maybePrint()
|
||||
if hp < 21:
|
||||
self.avatarCount[hp] += 1
|
||||
self.hoodsVisited[hp][numHoods] += 1
|
||||
self.safeZonesVisited[hp][numSafeZones] += 1
|
||||
|
||||
|
||||
"""
|
||||
import UtilityStart
|
||||
import RepairAvatars
|
||||
r = RepairAvatars.AvatarFixer(simbase.air)
|
||||
r.start()
|
||||
|
||||
|
||||
import UtilityStart
|
||||
import RepairAvatars
|
||||
r = RepairAvatars.AvatarPrinter(simbase.air)
|
||||
h = RepairAvatars.HoodInfoStore()
|
||||
r.start()
|
||||
|
||||
|
||||
|
||||
|
||||
for avId, backupDna in backupDict.items():
|
||||
newDna = newDict.get(avId):
|
||||
if newDna and (newDna != backupDna):
|
||||
print "================"
|
||||
print avId
|
||||
print backupDna
|
||||
print newDna
|
||||
|
||||
from toontown.toon import DistributedToonAI
|
||||
from toontown.toon import ToonDNA
|
||||
from toontown.toon import InventoryBase
|
||||
def fixAv(doId):
|
||||
av = DistributedToonAI.DistributedToonAI(simbase.air)
|
||||
av.doId = doId
|
||||
print doId
|
||||
av.inventory = InventoryBase.InventoryBase(av)
|
||||
av.teleportZoneArray = []
|
||||
db = DatabaseObject.DatabaseObject(simbase.air, av.doId)
|
||||
db.fillin(av, simbase.air.dclassesByName['DistributedToonAI'])
|
||||
oldD = oldDna.get(doId)
|
||||
print 'backup DNA', oldD
|
||||
newD = ToonDNA.ToonDNA()
|
||||
newD.newToonFromProperties(*oldD)
|
||||
print ' new DNA', newD.asTuple()
|
||||
av.b_setDNAString(newD.makeNetString())
|
||||
db.storeObject(av, ["setDNAString"])
|
||||
print 'done'
|
||||
|
||||
def fixAv(doId):
|
||||
|
||||
av = DistributedToonAI.DistributedToonAI(simbase.air)
|
||||
av.doId = doId
|
||||
db = DatabaseObject.DatabaseObject(simbase.air, av.doId)
|
||||
db.getFields(db.getDatabaseFields(simbase.air.dclassesByName['DistributedToonAI']))
|
||||
db.fillin(av, simbase.air.dclassesByName['DistributedToonAI'])
|
||||
print doId
|
||||
progressId, progress = av.getTrackProgress()
|
||||
trackAccess = av.getTrackAccess()
|
||||
print "old progressId: %s progress: %s" % (progressId, progress)
|
||||
print "trackAccess: ", trackAccess
|
||||
print "Tier: ", av.rewardTier
|
||||
# av.b_setTrackProgress(trackId, progress)
|
||||
# db.storeObject(av, ["setTrackProgress"])
|
||||
print 'done'
|
||||
|
||||
"""
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import time
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.PyDatagram import PyDatagram
|
||||
from panda3d.core import *
|
||||
from panda3d.toontown import *
|
||||
|
||||
from otp.ai.AIZoneData import AIZoneDataStore
|
||||
from otp.ai.AIMsgTypes import *
|
||||
from otp.ai.TimeManagerAI import TimeManagerAI
|
||||
from otp.distributed.OtpDoGlobals import *
|
||||
from toontown.ai.HolidayManagerAI import HolidayManagerAI
|
||||
|
|
@ -33,6 +36,7 @@ from toontown.hood.LawbotHQDataAI import LawbotHQDataAI
|
|||
from toontown.hood.MMHoodDataAI import MMHoodDataAI
|
||||
from toontown.hood.OZHoodDataAI import OZHoodDataAI
|
||||
from toontown.hood.TTHoodDataAI import TTHoodDataAI
|
||||
from toontown.parties.ToontownTimeManager import ToontownTimeManager
|
||||
from toontown.pets.PetManagerAI import PetManagerAI
|
||||
from toontown.quest.QuestManagerAI import QuestManagerAI
|
||||
from toontown.racing import RaceGlobals
|
||||
|
|
@ -45,6 +49,7 @@ from toontown.racing.DistributedViewPadAI import DistributedViewPadAI
|
|||
from toontown.racing.RaceManagerAI import RaceManagerAI
|
||||
from toontown.uberdog.DistributedPartyManagerAI import DistributedPartyManagerAI
|
||||
from toontown.safezone.SafeZoneManagerAI import SafeZoneManagerAI
|
||||
from toontown.safezone import DistributedPartyGateAI
|
||||
from toontown.shtiker.CogPageManagerAI import CogPageManagerAI
|
||||
from toontown.spellbook.ToontownMagicWordManagerAI import ToontownMagicWordManagerAI
|
||||
from toontown.suit.SuitInvasionManagerAI import SuitInvasionManagerAI
|
||||
|
|
@ -181,6 +186,11 @@ class ToontownAIRepository(ToontownInternalRepository):
|
|||
# Create our Cog suit manager...
|
||||
self.cogSuitMgr = CogSuitManagerAI(self)
|
||||
|
||||
# Create our Toontown time manager...
|
||||
self.toontownTimeManager = ToontownTimeManager()
|
||||
self.toontownTimeManager.updateLoginTimes(time.time(), time.time(), globalClock.getRealTime())
|
||||
|
||||
|
||||
def createGlobals(self):
|
||||
"""
|
||||
Creates "global" (distributed) objects.
|
||||
|
|
@ -406,7 +416,24 @@ class ToontownAIRepository(ToontownInternalRepository):
|
|||
return [] # TODO
|
||||
|
||||
def findPartyHats(self, dnaData, zoneId):
|
||||
return [] # TODO
|
||||
partyHats = []
|
||||
if 'party_gate' in dnaData.getName():
|
||||
x, y, z = dnaData.getPos()
|
||||
h, p, r = dnaData.getHpr()
|
||||
partyHat = DistributedPartyGateAI.DistributedPartyGateAI(self)
|
||||
partyHat.generateWithRequired(zoneId)
|
||||
partyHats.append(partyHat)
|
||||
else:
|
||||
if isinstance(dnaData, DNAVisGroup):
|
||||
name = dnaData.getName()
|
||||
visId = int(name.split(":", 1)[0]) % 1000
|
||||
zoneId = ZoneUtil.getHoodId(zoneId) + visId
|
||||
|
||||
for i in range(dnaData.getNumChildren()):
|
||||
foundPartyHats = self.findPartyHats(dnaData.at(i), zoneId)
|
||||
partyHats.extend(foundPartyHats)
|
||||
|
||||
return partyHats
|
||||
|
||||
def findRacingPads(self, dnaData, zoneId, area, type='racing_pad', overrideDNAZone=False):
|
||||
kartPads, kartPadGroups = [], []
|
||||
|
|
@ -506,3 +533,73 @@ class ToontownAIRepository(ToontownInternalRepository):
|
|||
def setupFiles(self):
|
||||
if not os.path.exists(self.dataFolder):
|
||||
os.mkdir(self.dataFolder)
|
||||
|
||||
# From Anesidora
|
||||
def sendUpdateToDoId(self, dclassName, fieldName, doId, args, channelId=None):
|
||||
"""
|
||||
channelId can be used as a recipient if you want to bypass the normal
|
||||
airecv, ownrecv, broadcast, etc. If you don't include a channelId
|
||||
or if channelId == doId, then the normal broadcast options will
|
||||
be used.
|
||||
|
||||
See Also: def queryObjectField
|
||||
"""
|
||||
dclass=self.dclassesByName.get(dclassName+self.dcSuffix)
|
||||
assert dclass is not None
|
||||
if channelId is None:
|
||||
channelId=doId
|
||||
if dclass is not None:
|
||||
dg = dclass.aiFormatUpdate(
|
||||
fieldName, doId, channelId, self.ourChannel, args)
|
||||
self.send(dg)
|
||||
|
||||
def createDgUpdateToDoId(self, dclassName, fieldName, doId, args,
|
||||
channelId=None):
|
||||
"""
|
||||
channelId can be used as a recipient if you want to bypass the normal
|
||||
airecv, ownrecv, broadcast, etc. If you don't include a channelId
|
||||
or if channelId == doId, then the normal broadcast options will
|
||||
be used.
|
||||
|
||||
This is just like sendUpdateToDoId, but just returns
|
||||
the datagram instead of immediately sending it.
|
||||
"""
|
||||
result = None
|
||||
dclass=self.dclassesByName.get(dclassName+self.dcSuffix)
|
||||
assert dclass is not None
|
||||
if channelId is None:
|
||||
channelId=doId
|
||||
if dclass is not None:
|
||||
dg = dclass.aiFormatUpdate(
|
||||
fieldName, doId, channelId, self.ourChannel, args)
|
||||
result = dg
|
||||
return result
|
||||
|
||||
def sendUpdateToGlobalDoId(self, dclassName, fieldName, doId, args):
|
||||
"""
|
||||
Used for sending messages from an AI directly to an
|
||||
uber object.
|
||||
"""
|
||||
dclass = self.dclassesByName.get(dclassName)
|
||||
assert dclass, 'dclass %s not found in DC files' % dclassName
|
||||
dg = dclass.aiFormatUpdate(
|
||||
fieldName, doId, doId, self.ourChannel, args)
|
||||
self.send(dg)
|
||||
|
||||
def addPostSocketClose(self, themessage):
|
||||
# Time to send a register for channel message to the msgDirector
|
||||
datagram = PyDatagram()
|
||||
# datagram.addServerControlHeader(CONTROL_ADD_POST_REMOVE)
|
||||
datagram.addInt8(1)
|
||||
datagram.addChannel(CONTROL_MESSAGE)
|
||||
datagram.addUint16(CONTROL_ADD_POST_REMOVE)
|
||||
|
||||
datagram.addBlob(themessage.getMessage())
|
||||
self.send(datagram)
|
||||
|
||||
def addPostSocketCloseUD(self, dclassName, fieldName, doId, args):
|
||||
dclass = self.dclassesByName.get(dclassName)
|
||||
assert dclass, 'dclass %s not found in DC files' % dclassName
|
||||
dg = dclass.aiFormatUpdate(
|
||||
fieldName, doId, doId, self.ourChannel, args)
|
||||
self.addPostSocketClose(dg)
|
||||
|
|
@ -14,3 +14,23 @@ class ToontownInternalRepository(OTPInternalRepository):
|
|||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Anesidora
|
||||
def sendUpdateToDoId(self, dclassName, fieldName, doId, args,
|
||||
channelId=None):
|
||||
"""
|
||||
channelId can be used as a recipient if you want to bypass the normal
|
||||
airecv, ownrecv, broadcast, etc. If you don't include a channelId
|
||||
or if channelId == doId, then the normal broadcast options will
|
||||
be used.
|
||||
|
||||
See Also: def queryObjectField
|
||||
"""
|
||||
dclass=self.dclassesByName.get(dclassName+self.dcSuffix)
|
||||
assert dclass is not None
|
||||
if channelId is None:
|
||||
channelId=doId
|
||||
if dclass is not None:
|
||||
dg = dclass.aiFormatUpdate(
|
||||
fieldName, doId, channelId, self.ourChannel, args)
|
||||
self.send(dg)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import math
|
||||
from panda3d.core import *
|
||||
from panda3d.otp import Nametag, NametagFloat3d
|
||||
from direct.interval.MetaInterval import Sequence, Parallel
|
||||
from direct.interval.FunctionInterval import Func
|
||||
from direct.interval.LerpInterval import LerpScaleInterval, LerpColorScaleInterval
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ class DistributedParty(DistributedObject.DistributedObject):
|
|||
def loadDecorations(self):
|
||||
self.decorationsList = []
|
||||
for decorBase in self.partyInfo.decors:
|
||||
self.decorationsList.append(Decoration(PartyGlobals.DecorationIds.getString(decorBase.decorId), PartyUtils.convertDistanceFromPartyGrid(decorBase.x, 0), PartyUtils.convertDistanceFromPartyGrid(decorBase.y, 1), PartyUtils.convertDegreesFromPartyGrid(decorBase.h)))
|
||||
self.decorationsList.append(Decoration(PartyGlobals.DecorationIds(decorBase.decorId).name, PartyUtils.convertDistanceFromPartyGrid(decorBase.x, 0), PartyUtils.convertDistanceFromPartyGrid(decorBase.y, 1), PartyUtils.convertDegreesFromPartyGrid(decorBase.h)))
|
||||
|
||||
def unload(self):
|
||||
if hasattr(self, 'decorationsList') and self.decorationsList:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,282 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
# from Anesidora
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Shawn Patton
|
||||
# Created: Sep 2008
|
||||
#
|
||||
# Purpose: DistributedPartyAI controls message passing to the client for parties
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
|
||||
from toontown.parties import PartyGlobals
|
||||
from toontown.parties import PartyUtils
|
||||
|
||||
from toontown.parties.DistributedPartyJukeboxActivityAI import DistributedPartyJukeboxActivityAI
|
||||
from toontown.parties.DistributedPartyCannonActivityAI import DistributedPartyCannonActivityAI
|
||||
from toontown.parties.DistributedPartyTrampolineActivityAI import DistributedPartyTrampolineActivityAI
|
||||
from toontown.parties.DistributedPartyVictoryTrampolineActivityAI import DistributedPartyVictoryTrampolineActivityAI
|
||||
from toontown.parties.DistributedPartyCatchActivityAI import DistributedPartyCatchActivityAI
|
||||
from toontown.parties.DistributedPartyDanceActivityAI import DistributedPartyDanceActivityAI
|
||||
from toontown.parties.DistributedPartyTugOfWarActivityAI import DistributedPartyTugOfWarActivityAI
|
||||
from toontown.parties.DistributedPartyFireworksActivityAI import DistributedPartyFireworksActivityAI
|
||||
from toontown.parties.DistributedPartyJukebox40ActivityAI import DistributedPartyJukebox40ActivityAI
|
||||
from toontown.parties.DistributedPartyDance20ActivityAI import DistributedPartyDance20ActivityAI
|
||||
from toontown.parties.DistributedPartyCogActivityAI import DistributedPartyCogActivityAI
|
||||
from toontown.parties.DistributedPartyWinterCatchActivityAI import DistributedPartyWinterCatchActivityAI
|
||||
from toontown.parties.DistributedPartyWinterTrampolineActivityAI import DistributedPartyWinterTrampolineActivityAI
|
||||
from toontown.parties.DistributedPartyWinterCogActivityAI import DistributedPartyWinterCogActivityAI
|
||||
from toontown.parties.DistributedPartyValentineDanceActivityAI import DistributedPartyValentineDanceActivityAI
|
||||
from toontown.parties.DistributedPartyValentineDance20ActivityAI import DistributedPartyValentineDance20ActivityAI
|
||||
from toontown.parties.DistributedPartyValentineJukeboxActivityAI import DistributedPartyValentineJukeboxActivityAI
|
||||
from toontown.parties.DistributedPartyValentineJukebox40ActivityAI import DistributedPartyValentineJukebox40ActivityAI
|
||||
from toontown.parties.DistributedPartyValentineTrampolineActivityAI import DistributedPartyValentineTrampolineActivityAI
|
||||
|
||||
ActivityIdsToClasses = {
|
||||
PartyGlobals.ActivityIds.PartyJukebox : DistributedPartyJukeboxActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyCannon : DistributedPartyCannonActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyTrampoline : DistributedPartyTrampolineActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyVictoryTrampoline : DistributedPartyVictoryTrampolineActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyCatch : DistributedPartyCatchActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyDance : DistributedPartyDanceActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyTugOfWar : DistributedPartyTugOfWarActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyFireworks : DistributedPartyFireworksActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyClock : None,
|
||||
PartyGlobals.ActivityIds.PartyJukebox40 : DistributedPartyJukebox40ActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyDance20 : DistributedPartyDance20ActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyCog : DistributedPartyCogActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyVictoryTrampoline: DistributedPartyVictoryTrampolineActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyWinterCatch: DistributedPartyWinterCatchActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyWinterTrampoline: DistributedPartyWinterTrampolineActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyWinterCog: DistributedPartyWinterCogActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyValentineDance: DistributedPartyValentineDanceActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyValentineDance20: DistributedPartyValentineDance20ActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyValentineJukebox: DistributedPartyValentineJukeboxActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyValentineJukebox40: DistributedPartyValentineJukebox40ActivityAI,
|
||||
PartyGlobals.ActivityIds.PartyValentineTrampoline: DistributedPartyValentineTrampolineActivityAI,
|
||||
}
|
||||
|
||||
class DistributedPartyAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyAI')
|
||||
notify = directNotify.newCategory("DistributedPartyAI")
|
||||
|
||||
def __init__(self, air, avId, zoneId, partyInfo, inviteeIds):
|
||||
DistributedObjectAI.__init__(self, air)
|
||||
|
||||
self.notify.debug("created with avId = %s, zoneId = %s, and partyInfo=%s" % (avId, zoneId, partyInfo))
|
||||
self.avId = avId
|
||||
self.zoneId = zoneId
|
||||
self.partyInfo = partyInfo
|
||||
self.inviteeIds = inviteeIds
|
||||
self.partyStartedTime = self.air.toontownTimeManager.getCurServerDateTime()
|
||||
self.partyClockInfo = (0,0,0)
|
||||
|
||||
# For the party clock, we need to set aside the x, y, and h
|
||||
for activity in self.partyInfo.activityList:
|
||||
if activity.activityId == PartyGlobals.ActivityIds.PartyClock:
|
||||
self.partyClockInfo = (activity.x, activity.y, activity.h)
|
||||
|
||||
self.hostName = ""
|
||||
# try to get the host name at this point here
|
||||
toon = simbase.air.doId2do.get(self.partyInfo.hostId)
|
||||
if toon:
|
||||
self.hostName = toon.getName()
|
||||
|
||||
# Keep track of the parties state for toon's coming in to the party
|
||||
self.isPartyEnding = False
|
||||
self.activityObjects = []
|
||||
|
||||
def getPartyState(self):
|
||||
return self.isPartyEnding
|
||||
|
||||
def b_setPartyState(self, partyState):
|
||||
self.isPartyEnding = partyState
|
||||
self.sendUpdate("setPartyState", [partyState])
|
||||
|
||||
def generate(self):
|
||||
DistributedPartyAI.notify.debug("DistParty generate: %s" % self.doId)
|
||||
DistributedObjectAI.generate(self)
|
||||
|
||||
self.air.writeServerEvent("party_generate",self.partyInfo.partyId, "%d|%d" % (self.doId,self.partyInfo.hostId))
|
||||
|
||||
# Log that a GM party has been generated.
|
||||
try:
|
||||
host = simbase.air.doId2do.get(self.partyInfo.hostId)
|
||||
if host.hasGMName():
|
||||
self.air.writeServerEvent("party_generate_gm", self.partyInfo.partyId, "%s" % self.partyInfo.hostId)
|
||||
assert self.notify.debug("GM-%s's party has started." % self.partyInfo.hostId)
|
||||
except:
|
||||
pass
|
||||
|
||||
# We want to initialize all the activities that are at this party.
|
||||
# We'll loop through the activityList and see if we can import, create,
|
||||
# and generate the relevant AI class. This code assumes that the
|
||||
# activity classes are named according to the enum
|
||||
# PartyGlobals.ActivityIds
|
||||
# for example:
|
||||
# PartyGlobals.ActivityIds.PartyCatch would load
|
||||
# DistributedPartyCatchActivityAI
|
||||
for activity in self.partyInfo.activityList:
|
||||
# if activity not in ActivityIdsToClasses:
|
||||
# self.notify.warning('DistributedPartyAI: unknown activity: {0} continuing to next one'.format(activity))
|
||||
# continue
|
||||
# Location and heading in the activityList is in party space, so
|
||||
# we convert them to Panda space before passing them into the
|
||||
# activities
|
||||
#activityName = PartyGlobals.ActivityIds.getString(activity.activityId)
|
||||
x = PartyUtils.convertDistanceFromPartyGrid(activity.x, 0)
|
||||
y = PartyUtils.convertDistanceFromPartyGrid(activity.y, 1)
|
||||
h = PartyUtils.convertDegreesFromPartyGrid(activity.h)
|
||||
# Skip the party clock...
|
||||
if activity.activityId == PartyGlobals.ActivityIds.PartyClock:
|
||||
continue
|
||||
|
||||
# Special case for cannon, add another cannon instead of creating
|
||||
# a new instance of the cannon activity
|
||||
if activity.activityId == PartyGlobals.ActivityIds.PartyCannon and \
|
||||
self.getCannonActivity():
|
||||
self.getCannonActivity().spawnCannonAt(x, y, h)
|
||||
continue
|
||||
|
||||
actClass = ActivityIdsToClasses[activity.activityId]
|
||||
newAct = actClass(self.air, self.doId, x, y ,h)
|
||||
newAct.generateWithRequired(self.zoneId)
|
||||
self.activityObjects.append(newAct)
|
||||
|
||||
def getCannonActivity(self):
|
||||
result = None
|
||||
for act in self.activityObjects:
|
||||
if "DistributedPartyCannonActivityAI" in str(act.__class__):
|
||||
result = act
|
||||
break
|
||||
return result
|
||||
|
||||
def getPartyClockInfo(self):
|
||||
return self.partyClockInfo
|
||||
|
||||
def getAvIdsAtParty(self):
|
||||
return self.air.partyManager.zoneIdToGuestAvIds[self.zoneId]
|
||||
|
||||
def getPartyStartedTime(self):
|
||||
return self.partyStartedTime.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def getInviteeIds(self):
|
||||
return self.inviteeIds
|
||||
|
||||
def getPartyInfoTuple(self):
|
||||
startTime = self.partyInfo.startTime
|
||||
endTime = self.partyInfo.endTime
|
||||
formattedActivities = []
|
||||
for activity in self.partyInfo.activityList:
|
||||
oneActivity = (activity.activityId,
|
||||
activity.x,
|
||||
activity.y,
|
||||
activity.h,
|
||||
)
|
||||
formattedActivities.append(oneActivity)
|
||||
formattedDecors = []
|
||||
for decor in self.partyInfo.decors:
|
||||
oneDecor = (decor.decorId,
|
||||
decor.x,
|
||||
decor.y,
|
||||
decor.h,
|
||||
)
|
||||
formattedDecors.append(oneDecor)
|
||||
isPrivate = self.partyInfo.isPrivate
|
||||
inviteTheme = self.partyInfo.inviteTheme
|
||||
|
||||
return (
|
||||
self.partyInfo.partyId,
|
||||
self.partyInfo.hostId,
|
||||
startTime.year,
|
||||
startTime.month,
|
||||
startTime.day,
|
||||
startTime.hour,
|
||||
startTime.minute,
|
||||
endTime.year,
|
||||
endTime.month,
|
||||
endTime.day,
|
||||
endTime.hour,
|
||||
endTime.minute,
|
||||
isPrivate,
|
||||
inviteTheme,
|
||||
formattedActivities,
|
||||
formattedDecors,
|
||||
self.partyInfo.status,
|
||||
)
|
||||
|
||||
def delete(self):
|
||||
DistributedPartyAI.notify.debug("DistParty delete: %s" % self.doId)
|
||||
self.ignoreAll()
|
||||
try:
|
||||
self.Party_deleted
|
||||
DistributedPartyAI.notify.debug("party already deleted: %s" % self.Party_deleted)
|
||||
except:
|
||||
DistributedPartyAI.notify.debug("completing party delete: %s" % self.__dict__.get("zoneId"))
|
||||
self.air.writeServerEvent("party_delete",self.partyInfo.partyId, "%d|%d" % (self.doId,self.partyInfo.hostId))
|
||||
self.Party_deleted = self.zoneId
|
||||
for activityObj in self.activityObjects:
|
||||
activityObj.requestDelete()
|
||||
self.activityObjects = []
|
||||
DistributedObjectAI.delete(self)
|
||||
|
||||
|
||||
def unload(self):
|
||||
self.notify.debug("unload")
|
||||
|
||||
def avIdEnteredParty(self, avId):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if senderId != avId:
|
||||
self.air.writeServerEvent('suspicious', senderId, 'someone else trying to enter a party for this avatar: avId = %d' % avId)
|
||||
return
|
||||
if not self.hostName and self.avId == self.partyInfo.hostId:
|
||||
# if we don't have a hostname yet try again
|
||||
toon = simbase.air.doId2do.get(self.partyInfo.hostId)
|
||||
if toon:
|
||||
toonName = toon.getName()
|
||||
if toonName:
|
||||
self.b_setHostName(toonName)
|
||||
|
||||
self.sendUpdate("setAvIdsAtParty", [self.air.partyManager.zoneIdToGuestAvIds[self.zoneId]])
|
||||
totalMoney = -1
|
||||
av = simbase.air.doId2do.get(avId)
|
||||
if av:
|
||||
totalMoney = av.getTotalMoney()
|
||||
self.air.writeServerEvent("party_enter",self.partyInfo.partyId, "%d|%d" % (avId,totalMoney))
|
||||
|
||||
def initPartyData(self):
|
||||
pass
|
||||
|
||||
def destroyPartyData(self):
|
||||
if hasattr(self, "Party_deleted"):
|
||||
DistributedPartyAI.notify.debug("destroyPartyData: party already deleted: %s" % self.Party_deleted)
|
||||
return
|
||||
DistributedPartyAI.notify.debug("destroyPartyData: %s" % self.__dict__.get("zoneId"))
|
||||
self.releaseZoneData()
|
||||
|
||||
def getHostName(self):
|
||||
"""Return the host name."""
|
||||
return self.hostName
|
||||
|
||||
def setHostName(self, newName):
|
||||
"""Set the hostname on the AI only."""
|
||||
self.hostName = self.hostName
|
||||
|
||||
def b_setHostName(self, newName):
|
||||
"""Set the hostname on the ai and client"""
|
||||
self.setHostName(newName)
|
||||
self.d_setHostName(newName)
|
||||
|
||||
def d_setHostName(self, newName):
|
||||
"""Send the host name to the client."""
|
||||
self.sendUpdate("setHostName", [newName])
|
||||
|
||||
def isInActivity(self, avId):
|
||||
"""Return true if the avId is busy with an activity."""
|
||||
result = False
|
||||
for actObj in self.activityObjects:
|
||||
if actObj.isInActivity(avId):
|
||||
result = True
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ class DistributedPartyActivity(DistributedObject.DistributedObject):
|
|||
def __init__(self, cr, activityId, activityType, wantLever = False, wantRewardGui = False):
|
||||
DistributedObject.DistributedObject.__init__(self, cr)
|
||||
self.activityId = activityId
|
||||
self.activityName = PartyGlobals.ActivityIds.getString(self.activityId)
|
||||
self.activityName = PartyGlobals.ActivityIds(self.activityId).name
|
||||
self.activityType = activityType
|
||||
self.wantLever = wantLever
|
||||
self.wantRewardGui = wantRewardGui
|
||||
|
|
@ -234,9 +234,9 @@ class DistributedPartyActivity(DistributedObject.DistributedObject):
|
|||
def loadSign(self):
|
||||
actNameForSign = self.activityName
|
||||
if self.activityId == PartyGlobals.ActivityIds.PartyJukebox40:
|
||||
actNameForSign = PartyGlobals.ActivityIds.getString(PartyGlobals.ActivityIds.PartyJukebox)
|
||||
actNameForSign = PartyGlobals.ActivityIds.PartyJukebox.name
|
||||
elif self.activityId == PartyGlobals.ActivityIds.PartyDance20:
|
||||
actNameForSign = PartyGlobals.ActivityIds.getString(PartyGlobals.ActivityIds.PartyDance)
|
||||
actNameForSign = PartyGlobals.ActivityIds.PartyDance.name
|
||||
self.sign = self.root.attachNewNode('%sSign' % self.activityName)
|
||||
self.signModel = self.party.defaultSignModel.copyTo(self.sign)
|
||||
self.signFlat = self.signModel.find('**/sign_flat')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,254 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
# From Anesidora
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Shawn Patton, Rob Gordon, Edmundo Ruiz (Schell Games)
|
||||
# Created: Sep 2008
|
||||
#
|
||||
# Purpose: DistributedPartyActivityAI is the base class for all party activities.
|
||||
# It loads up the sign and lever (where applicable)
|
||||
#-------------------------------------------------------------------------------
|
||||
from direct.distributed import DistributedObjectAI
|
||||
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyActivityAI(DistributedObjectAI.DistributedObjectAI):
|
||||
"""
|
||||
This is the base class for all Distributed Party Activities on the AI. A distributed
|
||||
party activity constitutes of any game or area at a party that involves multiple toons
|
||||
interacting with it at the same time.
|
||||
|
||||
Note that a new notify category is not created here as this class expects
|
||||
subclasses to create it.
|
||||
"""
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h, activityId, activityType):
|
||||
"""
|
||||
x, y and h are in Panda space, not Party Grid space.
|
||||
"""
|
||||
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
||||
|
||||
self.partyDoId = partyDoId
|
||||
self.party = self.air.doId2do.get(partyDoId)
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.h = h
|
||||
self.activityId = activityId
|
||||
self.activityName = PartyGlobals.ActivityIds(self.activityId).name
|
||||
self.activityType = activityType
|
||||
|
||||
# Actual avatars that will play the activity
|
||||
self.toonIds = []
|
||||
self.toonIdsToJellybeanRewards = {}
|
||||
self.toonId2joinTime = {}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# join/exit functions that subclasses should override
|
||||
#-------------------------------------------------------------------------------
|
||||
def toonJoinRequest(self):
|
||||
"""
|
||||
Clients call this over the wire when they want to join/start this
|
||||
activity. Subclasses should override this and call sendToonJoinResponse.
|
||||
"""
|
||||
self.notify.error("BASE: toonJoinRequest should be overridden")
|
||||
|
||||
|
||||
def toonExitRequest(self):
|
||||
"""
|
||||
Clients call this over the wire when they want to exit this
|
||||
activity, but need AI server permission to do so.
|
||||
Subclasses should override this and call sendToonExitResponse.
|
||||
"""
|
||||
self.notify.error("BASE: toonExitRequest should be overridden")
|
||||
|
||||
|
||||
def toonReady(self):
|
||||
"""
|
||||
Clients call this over the wire when they are done reading the rules
|
||||
and are ready to play the activity. Subclasses should override this.
|
||||
"""
|
||||
self.notify.error("BASE: toonReady should be overridden")
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def toonExitDemand(self):
|
||||
"""
|
||||
Clients call this over the wire to inform the server that the sender
|
||||
has left the activity. Use this for activities where server permission
|
||||
is not required for leaving the activity.
|
||||
|
||||
A default implementation is provided, which may be extended or
|
||||
overridden by subclasses.
|
||||
"""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.sendToonExitResponse(senderId, True)
|
||||
|
||||
|
||||
def sendToonJoinResponse(self, toonId, joined, denialReason=PartyGlobals.DenialReasons.Default):
|
||||
"""
|
||||
Subclasses should call this in response to recieving a toonJoinRequest
|
||||
call.
|
||||
|
||||
Parameters:
|
||||
toonId- id of the avatar that is being accepted/rejected
|
||||
joined- True if the avatar can join this activity, False otherwise
|
||||
"""
|
||||
if joined:
|
||||
self._addToon(toonId)
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds])
|
||||
else:
|
||||
self.sendUpdateToAvatarId(toonId, "joinRequestDenied", [denialReason])
|
||||
|
||||
|
||||
def sendToonExitResponse(self, toonId, exited, denialReason=PartyGlobals.DenialReasons.Default):
|
||||
"""
|
||||
Broadcast to all clients whether or not this toonId has left the activity.
|
||||
Subclasses should call this in response to recieving a toonExitRequest
|
||||
call.
|
||||
Parameters:
|
||||
toonId- id of the avatar that made the request to exit
|
||||
exited- True if the avatar exited this activity, False otherwise
|
||||
"""
|
||||
if exited:
|
||||
self._removeToon(toonId)
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds])
|
||||
else:
|
||||
self.sendUpdateToAvatarId(toonId, "exitRequestDenied", [denialReason])
|
||||
|
||||
|
||||
def isToonPlaying(self, toonId):
|
||||
return(toonId in self.toonIds)
|
||||
|
||||
|
||||
def removeAllToons(self):
|
||||
"""
|
||||
Base classes should call this when an activity is over and all toons
|
||||
should exit.
|
||||
"""
|
||||
self.notify.debug("BASE: removeAllToons %s" % self.toonIds)
|
||||
while len(self.toonIds):
|
||||
self._removeToon(self.toonIds[0])
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds])
|
||||
|
||||
|
||||
def _addToon(self, toonId):
|
||||
"""
|
||||
Utility function for adding an avatar to the list of playing avatars.
|
||||
This call should be the only way you add an toonId to self.toonIds.
|
||||
"""
|
||||
self.notify.debug("BASE: _addToon( toonId=%s )" % toonId)
|
||||
if toonId in self.toonIds:
|
||||
self.notify.warning("BASE: attempt to add toonId (%s) failed as it was already in self.toonIds" % toonId)
|
||||
else:
|
||||
self.toonIds.append(toonId)
|
||||
self.toonId2joinTime[toonId] = globalClock.getFrameTime()
|
||||
# listen for this avatar's exit event
|
||||
self.acceptOnce(
|
||||
self.air.getAvatarExitEvent(toonId),
|
||||
self._handleUnexpectedToonExit,
|
||||
extraArgs=[toonId],
|
||||
)
|
||||
|
||||
|
||||
def _handleUnexpectedToonExit(self, toonId):
|
||||
"""
|
||||
An avatar bailed out because he lost his connection or quit
|
||||
unexpectedly. Sub-classes may need to extend this function.
|
||||
"""
|
||||
self.notify.debug("BASE: _handleUnexpectedToonExit( toonId=%s )" % toonId)
|
||||
# bypass any subclass checks and let clients know the toon has left the
|
||||
# activity
|
||||
self._removeToon(toonId)
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds])
|
||||
|
||||
|
||||
def _removeToon(self, toonId):
|
||||
"""
|
||||
Utility function for removing an avatar from the list of playing avatars.
|
||||
This call should be the only way you remove an toonId from
|
||||
self.toonIds.
|
||||
"""
|
||||
self.notify.debug("BASE: _removeToon( toonId=%s )" % toonId)
|
||||
if toonId not in self.toonIds:
|
||||
self.notify.warning("BASE: attempt to remove toonId (%s) failed as it was not in toonIds" % toonId)
|
||||
else:
|
||||
self.toonIds.remove(toonId)
|
||||
del self.toonId2joinTime[toonId]
|
||||
# stop listening for this avatar's exit event
|
||||
self.ignore(self.air.getAvatarExitEvent(toonId))
|
||||
# don't delete scores here, as we may want to display score info
|
||||
# for dropped players.
|
||||
|
||||
|
||||
def generate(self):
|
||||
DistributedObjectAI.DistributedObjectAI.generate(self)
|
||||
self.notify.debug("BASE: generate")
|
||||
|
||||
|
||||
def announceGenerate(self):
|
||||
DistributedObjectAI.DistributedObjectAI.announceGenerate(self)
|
||||
self.notify.debug("BASE: announceGenerate")
|
||||
|
||||
|
||||
def delete(self):
|
||||
self.notify.debug("BASE: delete: deleting AI PartyActivity object")
|
||||
self.ignoreAll()
|
||||
DistributedObjectAI.DistributedObjectAI.delete(self)
|
||||
|
||||
|
||||
def getPartyDoId(self):
|
||||
return self.partyDoId
|
||||
|
||||
|
||||
def getX(self):
|
||||
return self.x
|
||||
|
||||
|
||||
def getY(self):
|
||||
return self.y
|
||||
|
||||
|
||||
def getH(self):
|
||||
return self.h
|
||||
|
||||
|
||||
def issueJellybeanRewards(self):
|
||||
"""
|
||||
Give the participating toons their jellybean reward.
|
||||
"""
|
||||
now = globalClock.getFrameTime()
|
||||
while len(self.toonIdsToJellybeanRewards):
|
||||
self.issueJellybeanRewardToToonId(list(self.toonIdsToJellybeanRewards.keys())[0], now=now)
|
||||
|
||||
def issueJellybeanRewardToToonId(self, toonId, now=None):
|
||||
"""
|
||||
Give a specific toon a their jellybean reward.
|
||||
"""
|
||||
if toonId in self.toonIdsToJellybeanRewards:
|
||||
if now is None:
|
||||
now = globalClock.getFrameTime()
|
||||
reward = self.toonIdsToJellybeanRewards[toonId]
|
||||
timePlayed = now - self.toonId2joinTime[toonId]
|
||||
# reset 'joined' time to now, in case we give this toon another reward
|
||||
self.toonId2joinTime[toonId] = now
|
||||
self.__issueJellybeanReward(toonId, reward, timePlayed)
|
||||
del self.toonIdsToJellybeanRewards[toonId]
|
||||
|
||||
def __issueJellybeanReward(self, toonId, reward, timePlayed):
|
||||
"""
|
||||
Issue a toon a jellybean reward.
|
||||
"""
|
||||
# some activities, like Party Catch, can be left before it is over,
|
||||
# so we do not check whether the toonId is in the list of toons in
|
||||
# the activity.
|
||||
toon = self.air.doId2do.get(toonId)
|
||||
if toon is not None:
|
||||
self.notify.debug("rewarding %d jellybeans to %s" %(reward, toon.getName()))
|
||||
toon.addMoney(reward)
|
||||
self.air.writeServerEvent("party_reward", toonId, "%d|%d|%d" % (self.activityId, reward, timePlayed))
|
||||
|
||||
def isInActivity(self, avId):
|
||||
"""Return true if the avId is busy with us."""
|
||||
result = False
|
||||
if avId in self.toonIds:
|
||||
result = True
|
||||
return result
|
||||
|
||||
class DistributedPartyActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyActivityAI')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,236 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: DistributedPartyCannonAI handles the AI instance of a cannon at a party.
|
||||
# It keeps track if a toon is inside the cannon. Note that it does not
|
||||
# keep track of flying toons; that's the job of DistributedPartyCannonActivityAI
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyCannonAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCannonAI')
|
||||
from direct.distributed.ClockDelta import *
|
||||
from direct.task import Task
|
||||
from direct.distributed import DistributedObjectAI
|
||||
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
from toontown.minigame import CannonGameGlobals
|
||||
from toontown.minigame import Trajectory
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyCannonAI(DistributedObjectAI.DistributedObjectAI):
|
||||
notify = directNotify.newCategory("DistributedPartyCannonAI")
|
||||
#notify.setDebug(True)
|
||||
|
||||
CANNON_LIT_EVENT = "D_PARTY_CANNON_LIT"
|
||||
|
||||
def __init__(self, air, activityDoId, x, y, z, h, p, r):
|
||||
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
||||
|
||||
# TODO: Take this out when all cannons come from db
|
||||
z = 0
|
||||
self.posHpr = [x, y, z, h, p, r]
|
||||
self.toonInsideAvId = 0
|
||||
self.activityDoId = activityDoId
|
||||
self.timeoutTask = None
|
||||
self.rotation = 0.0
|
||||
self.angle = 0.0
|
||||
self.lit = False
|
||||
self.toonEnteredTime = 0
|
||||
|
||||
def delete(self):
|
||||
self.ignoreAll()
|
||||
self.__stopTimeout()
|
||||
DistributedObjectAI.DistributedObjectAI.delete(self)
|
||||
|
||||
# Generate is never called on the AI so we do not define one
|
||||
# Disable is never called on the AI so we do not define one
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def requestEnter(self):
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
# No toon inside, may enter cannon
|
||||
inActivity = False
|
||||
parentActivity = simbase.air.doId2do.get(self.activityDoId)
|
||||
if parentActivity:
|
||||
inActivity = parentActivity.party.isInActivity(avId)
|
||||
if not self.isToonInside() and not inActivity:
|
||||
self.__placeToonInside(avId)
|
||||
else:
|
||||
if self.toonInsideAvId != avId:
|
||||
# don't kick out a toon already inside the cannon
|
||||
self.notify.debug("requestEnter() - cannon already occupied")
|
||||
self.sendUpdateToAvatarId(avId, "requestExit", [])
|
||||
else:
|
||||
self.notify.warning("got requestEnter from toon already inside it.")
|
||||
|
||||
#===============================================================================
|
||||
# Attributes
|
||||
#===============================================================================
|
||||
def isToonInside(self):
|
||||
return (self.toonInsideAvId != 0)
|
||||
|
||||
def getToonInsideId(self):
|
||||
return self.toonInsideAvId
|
||||
|
||||
def isReadyToFire(self):
|
||||
return self.isToonInside() and self.lit
|
||||
|
||||
def __placeToonInside(self, avId):
|
||||
"""
|
||||
Prepares a toon to be inside the cannon.
|
||||
"""
|
||||
self.notify.debug("__placeToonInside %s" % avId)
|
||||
self.toonEnteredTime = globalClock.getFrameTime()
|
||||
self.__stopTimeout()
|
||||
self.toonInsideAvId = avId
|
||||
self.notify.debug("Sending PartyGlobals.CANNON_MOVIE_LOAD for %d" % avId)
|
||||
self.d_setMovie(PartyGlobals.CANNON_MOVIE_LOAD, self.toonInsideAvId)
|
||||
|
||||
# Handle unexpected exit
|
||||
self.acceptOnce(self.air.getAvatarExitEvent(avId),
|
||||
self.__handleUnexpectedExit, extraArgs=[avId])
|
||||
self.acceptOnce("bootAvFromParty-%d" % avId,
|
||||
self.__handleBootMessage, extraArgs=[avId])
|
||||
|
||||
def __clearToonInside(self):
|
||||
self.notify.debug("%d __clearToonInside self.toonInsideAvId=%d" % (self.doId, self.toonInsideAvId))
|
||||
self.toonInsideAvId = 0
|
||||
self.lit = False
|
||||
self.__stopTimeout()
|
||||
|
||||
# Distributed (broadcast, ram)
|
||||
def d_setMovie(self, mode, avId):
|
||||
self.notify.debug("d_setMovie mode=%s, avId=%s" % (str(mode), str(avId)))
|
||||
#if mode == PartyGlobals.CANNON_MOVIE_CLEAR:
|
||||
# from otp.otpbase.PythonUtil import StackTrace
|
||||
# print StackTrace()
|
||||
self.sendUpdate("setMovie", [mode, avId])
|
||||
|
||||
# Distributed (aisend clrecv)
|
||||
def setCannonPosition(self, zRot, angle):
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
# a client is sending a position update for their cannon
|
||||
#self.notify.debug("setCannonPosition: %d: zRot = %d, angle = %d" % (avId, zRot, angle))
|
||||
self.rotation = zRot
|
||||
self.angle = angle
|
||||
self.d_updateCannonPosition(avId)
|
||||
|
||||
# Distributed (broadcast ram)
|
||||
def d_updateCannonPosition(self, avId):
|
||||
self.sendUpdate("updateCannonPosition", [avId, self.rotation, self.angle])
|
||||
|
||||
# Distributed (required broadcast ram)
|
||||
def getPosHpr(self):
|
||||
return self.posHpr
|
||||
|
||||
# Distributed (required broadcast ram)
|
||||
# Needs this because setPartyId is dc required.
|
||||
def getActivityDoId(self):
|
||||
return self.activityDoId
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
# a client is telling us that their cannon's fuse is lit
|
||||
def setCannonLit(self, zRot, angle):
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
self.__cannonIsLit(avId, zRot, angle)
|
||||
|
||||
def __cannonIsLit(self, avId, zRot, angle):
|
||||
if avId == self.toonInsideAvId:
|
||||
self.__stopTimeout()
|
||||
self.lit = True
|
||||
self.rotation = zRot
|
||||
self.angle = angle
|
||||
self.notify.debug("__cannonIsLit: %d: zRot = %d, angle = %d" % (avId, zRot, angle))
|
||||
# Send out that this cannon is ready to fire.
|
||||
messenger.send(DistributedPartyCannonAI.CANNON_LIT_EVENT, [self.doId, self.toonEnteredTime])
|
||||
else:
|
||||
self.notify.warning("__cannonIsLit but avId=%s toonInsideAvId=%s" % (str(avId), str(self.toonInsideAvId)))
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def setFired(self):
|
||||
assert(self.notify.debug("%s setFired" % self.doId))
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if self.lit:
|
||||
self.ignore(self.air.getAvatarExitEvent(senderId))
|
||||
self.ignore("bootAvFromParty-%d" % senderId)
|
||||
self.notify.debug("sending PartyGlobals.CANNON_MOVIE_CLEAR for %d" % self.toonInsideAvId)
|
||||
self.d_setMovie(PartyGlobals.CANNON_MOVIE_CLEAR, self.toonInsideAvId)
|
||||
self.__clearToonInside()
|
||||
else:
|
||||
self.notify.warning("%d setFired called but not lit" % self.doId)
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def setLanded(self, avId):
|
||||
assert(self.notify.debug("%s setLanded" % self.doId))
|
||||
self.ignore(self.air.getAvatarExitEvent(avId))
|
||||
self.ignore("bootAvFromParty-%d" % avId)
|
||||
self.notify.debug("%d sending PartyGlobals.CANNON_MOVIE_LANDED for %d" % (self.doId,avId))
|
||||
self.d_setMovie(PartyGlobals.CANNON_MOVIE_LANDED, avId)
|
||||
self.__clearToonInside()
|
||||
|
||||
#===============================================================================
|
||||
# Functions
|
||||
#===============================================================================
|
||||
# Distributed (clsend airecv)
|
||||
def setTimeout(self):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if senderId == self.toonInsideAvId:
|
||||
self.__startTimeout(PartyGlobals.CANNON_TIMEOUT + 3)
|
||||
|
||||
def __startTimeout(self, timeLimit):
|
||||
# Sets the timeout counter running. If __stopTimeout() is not
|
||||
# called before the time expires, we'll exit the avatar. This
|
||||
# prevents avatars from hanging out in the fishing spot all
|
||||
# day.
|
||||
self.notify.debug("__startTimeout %s" % self.taskName("timeout"))
|
||||
self.__stopTimeout()
|
||||
self.notify.debug("done calling __stopTimeout, really starting it %s" % self.taskName("timeout"))
|
||||
self.timeoutTask = taskMgr.doMethodLater(timeLimit,
|
||||
self.__handleTimeoutTask,
|
||||
self.taskName("timeout"))
|
||||
|
||||
def __stopTimeout(self):
|
||||
"""
|
||||
Stops a previously-set timeout from expiring.
|
||||
"""
|
||||
self.notify.debug("__stopTimeout %s" % self.taskName("timeout"))
|
||||
if self.timeoutTask != None:
|
||||
taskMgr.remove(self.timeoutTask)
|
||||
self.timeoutTask = None
|
||||
self.notify.debug("%d __stopTimeout successful" % self.doId)
|
||||
else:
|
||||
self.notify.debug("%d __stopTimeout no timeoutTask" % self.doId)
|
||||
|
||||
def __handleTimeoutTask(self, task):
|
||||
"""
|
||||
Called when a timeout expires, this sends the avatar home
|
||||
"""
|
||||
self.notify.warning('Timeout expired! toonInside=%s' % str(self.toonInsideAvId))
|
||||
#self.__cannonIsLit(self.toonInsideAvId, self.rotation, self.angle)
|
||||
self.__doExit()
|
||||
return Task.done
|
||||
|
||||
def __handleUnexpectedExit(self, avId):
|
||||
"""
|
||||
Handle in case the client crashes and the toon disappears, etc.
|
||||
"""
|
||||
self.notify.warning('avatar:' + str(avId) + ' has exited unexpectedly')
|
||||
if self.toonInsideAvId == avId:
|
||||
self.__doExit()
|
||||
self.__clearToonInside()
|
||||
|
||||
def __handleBootMessage(self, avId):
|
||||
self.notify.warning('avatar:' + str(avId) + ' got booted.')
|
||||
self.__doExit()
|
||||
|
||||
def __doExit(self):
|
||||
"""
|
||||
Take the avatar out of the cannon because he's been in it
|
||||
too long without firing.
|
||||
"""
|
||||
self.notify.debug("__doExit sending PartyGlobals.CANNON_MOVIE_FORCE_EXIT to %d" % self.toonInsideAvId)
|
||||
self.d_setMovie(PartyGlobals.CANNON_MOVIE_FORCE_EXIT, self.toonInsideAvId)
|
||||
self.__clearToonInside()
|
||||
|
||||
def forceInsideToonToExit(self):
|
||||
"""just call __doExit."""
|
||||
self.__doExit()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,215 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
# From Anesidora
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Sep 2008
|
||||
#
|
||||
# Purpose: DistributedPartyCannonActivityAI handles the creation of AI Dist. Cannons
|
||||
# as well as keeping track of which toons are currently flying, including
|
||||
# cleanup of those toons. It also listens for when a DCannonAI is lit,
|
||||
# so that it can be set to fire on the client side.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyCannonActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCannonActivityAI')
|
||||
from toontown.toonbase import TTLocalizer
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
from toontown.parties.DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from toontown.parties.DistributedPartyCannonAI import DistributedPartyCannonAI
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyCannonActivityAI(DistributedPartyActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyCannonActivityAI")
|
||||
#notify.setDebug(True)
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyActivityAI.__init__(self, air, partyDoId, x, y, h, PartyGlobals.ActivityIds.PartyCannon, PartyGlobals.ActivityTypes.Continuous)
|
||||
|
||||
# map of cannons by cannon doId
|
||||
self.cannons = {}
|
||||
# map of flying toon doIds to firing cannons doIds
|
||||
self.flyingToons = {}
|
||||
self.flyingToonCloudsHit = {}
|
||||
self.toonIdsToJellybeanRewards = {}
|
||||
|
||||
# Map of cloudNumber to rgb info
|
||||
self.cloudColors = {}
|
||||
|
||||
def announceGenerate(self):
|
||||
DistributedPartyActivityAI.announceGenerate(self)
|
||||
self.__initCannons()
|
||||
|
||||
def __initCannons(self):
|
||||
"""
|
||||
Initialize Cannon AI instances
|
||||
"""
|
||||
# create the first cannon. Subsequent cannons will be created through
|
||||
# spawnCannonAt called from the partyAI itself.
|
||||
self.spawnCannonAt(self.x, self.y, self.h)
|
||||
|
||||
# Listen for when a party cannon has been lit
|
||||
self.accept(DistributedPartyCannonAI.CANNON_LIT_EVENT, self.__handleFireCannon)
|
||||
|
||||
|
||||
def spawnCannonAt(self, x, y, h):
|
||||
cannon = DistributedPartyCannonAI(self.air, self.doId, x, y, 0, h, 0, 0)
|
||||
cannon.generateWithRequired(self.zoneId)
|
||||
self.cannons[cannon.doId] = cannon
|
||||
|
||||
def delete(self):
|
||||
self.ignoreAll()
|
||||
for cannon in list(self.cannons.values()):
|
||||
cannon.requestDelete()
|
||||
self.cannons.clear()
|
||||
self.flyingToons.clear()
|
||||
self.flyingToonCloudsHit.clear()
|
||||
self.toonIdsToJellybeanRewards.clear()
|
||||
DistributedPartyActivityAI.delete(self)
|
||||
|
||||
def __handleFireCannon(self, cannonId, timeEnteredCannon):
|
||||
"""
|
||||
Event handler triggered when a cannon is "lit"
|
||||
Sets cannon to fire
|
||||
"""
|
||||
# Confirm that cannon is lit, otherwise ignore and report suspicious behavior
|
||||
if cannonId in self.cannons and self.cannons[cannonId].isReadyToFire():
|
||||
cannon = self.cannons[cannonId]
|
||||
toonId = cannon.getToonInsideId()
|
||||
|
||||
if toonId and toonId not in self.flyingToons:
|
||||
self.flyingToons[toonId] = cannon.doId
|
||||
self.flyingToonCloudsHit[toonId] = 0
|
||||
self.toonIdsToJellybeanRewards[toonId] = 0
|
||||
self._addToon(toonId)
|
||||
# we override toonId2Join times and start it from the time he entered the cannon
|
||||
self.notify.debug("changing join time from %s to %s" % (self.toonId2joinTime[toonId], timeEnteredCannon))
|
||||
self.toonId2joinTime[toonId] = timeEnteredCannon
|
||||
else:
|
||||
self.notify.warning("Trying to fire toon %s who is already flying." % toonId)
|
||||
toonId = cannon.getToonInsideId()
|
||||
if toonId:
|
||||
self.notify.warning("toon is still inside cannon, forcing him out")
|
||||
cannon.forceInsideToonToExit()
|
||||
return
|
||||
|
||||
self.d_setCannonWillFire(cannonId, cannon.rotation, cannon.angle)
|
||||
else:
|
||||
if cannonId in self.cannons:
|
||||
self.notify.warning("__handleFireCannon failed self.cannons[%d].isReadyToFire() = False" % cannonId)
|
||||
cannon = self.cannons[cannonId]
|
||||
toonId = cannon.getToonInsideId()
|
||||
if toonId:
|
||||
self.notify.warning("Cannon is not lit, forcing him out")
|
||||
cannon.forceInsideToonToExit()
|
||||
else:
|
||||
self.notify.warning("__handleFireCannon failed self.cannons.has_key(%d) = False" % cannonId)
|
||||
|
||||
# TODO: Write case for suspicious cannon lit call
|
||||
|
||||
def _handleUnexpectedToonExit(self, toonId):
|
||||
"""
|
||||
Flying toon client exits, request cleanup.
|
||||
"""
|
||||
if toonId in self.flyingToons:
|
||||
self.notify.warning("Avatar %s has exited unexpectedly." % toonId)
|
||||
|
||||
# cannon_movie_force_exit will clean up the avatar on the client
|
||||
# and eventually call setLanded to complete the cleanup on AI
|
||||
self.d_setMovie(PartyGlobals.CANNON_MOVIE_FORCE_EXIT, toonId)
|
||||
self.__cleanupFlyingToon(toonId)
|
||||
DistributedPartyActivityAI._handleUnexpectedToonExit(self, toonId)
|
||||
|
||||
def __cleanupFlyingToon(self, toonId):
|
||||
if toonId in self.flyingToons:
|
||||
self.ignore(self.air.getAvatarExitEvent(toonId))
|
||||
del self.flyingToons[toonId]
|
||||
del self.flyingToonCloudsHit[toonId]
|
||||
self._removeToon(toonId)
|
||||
if toonId in self.toonIdsToJellybeanRewards:
|
||||
del self.toonIdsToJellybeanRewards[toonId]
|
||||
|
||||
#===============================================================================
|
||||
# Attributes
|
||||
#===============================================================================
|
||||
|
||||
# Distributed(clsend airecv)
|
||||
def cloudsColorRequest(self):
|
||||
self.notify.debug("cloudsColorRequest")
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
cloudColorList = []
|
||||
for key, value in list(self.cloudColors.items()):
|
||||
cloudColorList.append([key, value[0], value[1], value[2]])
|
||||
|
||||
self.d_cloudsColorResponse(senderId, cloudColorList)
|
||||
|
||||
def d_cloudsColorResponse(self, avId, cloudColorList):
|
||||
self.notify.debug("cloudsColorResponse %s" % cloudColorList)
|
||||
self.sendUpdateToAvatarId(avId, "cloudsColorResponse", [cloudColorList])
|
||||
|
||||
# Distributed (airecv clsend)
|
||||
def requestCloudHit(self, cloudNumber, r, g, b):
|
||||
self.notify.debug("requestCloudHit %d (%d, %d, %d)" % (cloudNumber, r, g, b))
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
|
||||
if senderId in self.flyingToonCloudsHit:
|
||||
self.flyingToonCloudsHit[senderId] += 1
|
||||
addedJellyBeans = PartyGlobals.CannonJellyBeanReward
|
||||
if self.air.holidayManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_DAY):
|
||||
addedJellyBeans *= 2
|
||||
self.toonIdsToJellybeanRewards[senderId] += addedJellyBeans
|
||||
if self.toonIdsToJellybeanRewards[senderId] > PartyGlobals.CannonMaxTotalReward:
|
||||
# put a cap so we don't go beyond uint8
|
||||
self.toonIdsToJellybeanRewards[senderId] = PartyGlobals.CannonMaxTotalReward
|
||||
|
||||
|
||||
self.cloudColors[cloudNumber] = (r, g, b)
|
||||
self.d_setCloudHit(cloudNumber, r, g, b)
|
||||
|
||||
# Distributed (broadcast)
|
||||
def d_setCloudHit(self, cloudNumber, r, g, b):
|
||||
self.sendUpdate('setCloudHit', [cloudNumber, r, g, b])
|
||||
|
||||
# Distributed (broadcast ram)
|
||||
def d_setMovie(self, mode, toonId):
|
||||
"""
|
||||
Broadcasts movie (state) of activity to client
|
||||
"""
|
||||
self.sendUpdate("setMovie", [mode, toonId])
|
||||
|
||||
# Distributed (broadcast)
|
||||
def d_setCannonWillFire(self, cannonId, zRot, angle):
|
||||
"""
|
||||
Broadcasts that a cannon is ready to fire a toon
|
||||
"""
|
||||
self.sendUpdate("setCannonWillFire", [cannonId, zRot, angle])
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def setLanded(self, toonId):
|
||||
"""
|
||||
From the client, a toon has landed. Cleanup the toon and inform all clients.
|
||||
"""
|
||||
self.notify.debug("%s setLanded %s" % (self.doId, toonId))
|
||||
if toonId in self.flyingToons:
|
||||
|
||||
cloudsHit = self.flyingToonCloudsHit[toonId]
|
||||
if cloudsHit:
|
||||
jellybeansWon = self.toonIdsToJellybeanRewards[toonId]
|
||||
resultsMessage = TTLocalizer.PartyCannonResults % (jellybeansWon, cloudsHit)
|
||||
self.sendUpdateToAvatarId(
|
||||
toonId,
|
||||
"showJellybeanReward",
|
||||
[jellybeansWon, self.air.doId2do[toonId].getMoney(), resultsMessage]
|
||||
)
|
||||
self.issueJellybeanRewardToToonId(toonId)
|
||||
|
||||
self.d_setMovie(PartyGlobals.CANNON_MOVIE_LANDED, toonId)
|
||||
self.__cleanupFlyingToon(toonId)
|
||||
|
||||
def isInActivity(self, avId):
|
||||
"""Return true if the avId is flying or inside a cannon."""
|
||||
result = False
|
||||
if avId in self.flyingToons:
|
||||
result = True
|
||||
else:
|
||||
for cannon in list(self.cannons.values()):
|
||||
if cannon.getToonInsideId() == avId:
|
||||
result = True
|
||||
break;
|
||||
return result
|
||||
|
|
@ -1,5 +1,355 @@
|
|||
#-------------------------------------------------------------------------------
|
||||
# Contact: Shawn Patton
|
||||
# Created: Sep 2008
|
||||
#
|
||||
# Purpose: The Party Catch Activity is like the trolley catch activity, only
|
||||
# party-wide. Toons not in an activity when the lever is pulled are
|
||||
# put in this activity and anyone can join in/leave. Fruit falls
|
||||
# beneath the party tree.
|
||||
#-------------------------------------------------------------------------------
|
||||
from direct.fsm import ClassicFSM, State
|
||||
from direct.fsm import State
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from direct.distributed.ClockDelta import globalClockDelta
|
||||
|
||||
class DistributedPartyCatchActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCatchActivityAI')
|
||||
from toontown.toonbase import TTLocalizer
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
from toontown.parties import PartyGlobals
|
||||
from toontown.ai.ToonBarrier import ToonBarrier
|
||||
from toontown.parties.DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from toontown.parties.DistributedPartyCatchActivityBase import DistributedPartyCatchActivityBase
|
||||
from toontown.parties.activityFSMs import CatchActivityFSM
|
||||
|
||||
class DistributedPartyCatchActivityAI(DistributedPartyActivityAI, DistributedPartyCatchActivityBase):
|
||||
# Notify category for Distributed Party Activitys
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPartyCatchActivityAI")
|
||||
|
||||
class Generation:
|
||||
def __init__(self, generation, startTime, numPlayers):
|
||||
self.generation = generation
|
||||
self.startTime = startTime
|
||||
self.numPlayers = numPlayers
|
||||
self.caughtList = [0,] * 20
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyActivityAI.__init__(self, air, partyDoId, x, y, h, PartyGlobals.ActivityIds.PartyCatch, PartyGlobals.ActivityTypes.HostInitiated)
|
||||
self.toonIdsToScores = {}
|
||||
# create state machine and set initial state
|
||||
self.activityFSM = CatchActivityFSM(self)
|
||||
|
||||
def getStartTimestamp(self):
|
||||
return 0
|
||||
|
||||
def getGenerations(self):
|
||||
return []
|
||||
|
||||
def generate(self):
|
||||
DistributedPartyActivityAI.generate(self)
|
||||
self._partyEndedEvent = self.air.partyManager.getPartyEndedEvent(self.party.partyInfo.hostId)
|
||||
self.accept(self._partyEndedEvent, self._handlePartyEnded)
|
||||
self.conclusionCountdownTask = self.uniqueName("PartyCatchConclusionCountdownTask")
|
||||
self.conclusionFinishTask = self.uniqueName("PartyCatchConclusionFinishTask")
|
||||
self.activityFSM.request("Idle")
|
||||
# game is always running
|
||||
self.activityFSM.request("Active")
|
||||
|
||||
def delete(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("delete")
|
||||
taskMgr.remove(self.conclusionCountdownTask)
|
||||
taskMgr.remove(self.conclusionFinishTask)
|
||||
del self.activityFSM
|
||||
self.ignore(self._partyEndedEvent)
|
||||
DistributedPartyActivityAI.delete(self)
|
||||
|
||||
def _handlePartyEnded(self):
|
||||
# since this activity lasts for the entire party, end the activity when the party ends
|
||||
# (don't wait until players get kicked out of the party)
|
||||
# make sure everyone gets their reward
|
||||
for toonId, reward in list(self.toonIdsToScores.items()):
|
||||
reward = self.toonIdsToScores[toonId]
|
||||
if reward > PartyGlobals.CatchMaxTotalReward:
|
||||
# put a cap so we don't go beyond something ridiculous
|
||||
reward = PartyGlobals.CatchMaxTotalReward
|
||||
self.toonIdsToJellybeanRewards[toonId] = reward
|
||||
self.sendUpdateToAvatarId(toonId, "showJellybeanReward", [
|
||||
reward, self.air.doId2do[toonId].getMoney(),
|
||||
TTLocalizer.PartyCatchRewardMessage %(self.toonIdsToScores[toonId],reward)])
|
||||
self.toonIdsToJellybeanRewards[toonId] = reward
|
||||
self.issueJellybeanRewardToToonId(toonId)
|
||||
self.removeAllToons()
|
||||
self.requestState('Conclusion')
|
||||
|
||||
def _setUpNextGenScheduleTask(self, startT, delay=None):
|
||||
if delay is None:
|
||||
delay = 0.
|
||||
nextGen = self._generation + 1
|
||||
task = taskMgr.doMethodLater(
|
||||
delay, Functor(self._scheduleNextGeneration, startT),
|
||||
'schedNextGen-%s-%s' % (self.doId, nextGen))
|
||||
# cancel any pending generations that start after this one
|
||||
for gen, item in list(self._schedTasks.items()):
|
||||
if item[0] > self._lastGenerationStartTime:
|
||||
taskMgr.remove(item[1])
|
||||
del self._schedTasks[gen]
|
||||
self._schedTasks[nextGen] = (startT, task)
|
||||
|
||||
def getNumPlayers(self):
|
||||
return len(self._playerIds)
|
||||
|
||||
def _scheduleNextGeneration(self, startT, task=None):
|
||||
# prune out generations that are expired
|
||||
tCutoff = (globalClock.getFrameTime() - self.activityStartTime) - self.generationDuration
|
||||
# add a minute of wiggle room for laggy client connections
|
||||
tCutoff -= 60.
|
||||
genIndices = list(self._id2gen.keys())
|
||||
genIndices.sort()
|
||||
for genIndex in genIndices:
|
||||
timestamp = self._id2gen[genIndex].startTime
|
||||
if timestamp <= tCutoff:
|
||||
del self._id2gen[genIndex]
|
||||
|
||||
self.calcDifficultyConstants(self.getNumPlayers())
|
||||
|
||||
# try to avoid exploits and griefing by timing this generation relative to the
|
||||
# previous generation
|
||||
timeDelta = startT - self._lastGenerationStartTime
|
||||
quantizedTimeDelta = int(timeDelta / self.DropPeriod) * self.DropPeriod
|
||||
if quantizedTimeDelta < timeDelta:
|
||||
quantizedTimeDelta += self.DropPeriod
|
||||
if __dev__:
|
||||
assert quantizedTimeDelta >= timeDelta
|
||||
quantizedStartT = self._lastGenerationStartTime + quantizedTimeDelta
|
||||
|
||||
self._generation += 1
|
||||
curGenStartTime = quantizedStartT
|
||||
|
||||
self._id2gen[self._generation] = self.Generation(self._generation, curGenStartTime, self.getNumPlayers())
|
||||
self._lastGenerationStartTime = curGenStartTime
|
||||
|
||||
# create the data list for the network
|
||||
generations = []
|
||||
genIndices = list(self._id2gen.keys())
|
||||
genIndices.sort()
|
||||
for genIndex in genIndices:
|
||||
timestamp = self._id2gen[genIndex].startTime + self.activityStartTime
|
||||
numPlayers = self._id2gen[genIndex].numPlayers
|
||||
generations.append([genIndex, globalClockDelta.localToNetworkTime(timestamp, bits=32), numPlayers])
|
||||
|
||||
self.sendUpdate('setGenerations', [generations])
|
||||
|
||||
nextStartT = curGenStartTime + self.generationDuration
|
||||
# sync up the generations relative to the game start time, to preserve the
|
||||
# drop frequency interest curve
|
||||
nextStartT = (nextStartT / PartyGlobals.CatchActivityDuration) * PartyGlobals.CatchActivityDuration
|
||||
delay = ((nextStartT + self.activityStartTime) - globalClock.getRealTime()) * .5
|
||||
delay = max(delay, 0.)
|
||||
self._genSchedTask = self._setUpNextGenScheduleTask(nextStartT, delay)
|
||||
|
||||
## def requestActivityStart(self):
|
||||
## """
|
||||
## Called by the client when the host pulls the lever to start this game.
|
||||
## """
|
||||
## DistributedPartyCatchActivityAI.notify.debug("requestActivityStart")
|
||||
## senderId = self.air.getAvatarIdFromSender()
|
||||
## if self.activityFSM.state == "Idle":
|
||||
## self.activityFSM.request("Active")
|
||||
## self.sendUpdateToAvatarId(senderId, "startRequestResponse", [int(True)])
|
||||
## else:
|
||||
## self.sendUpdateToAvatarId(senderId, "startRequestResponse", [int(False)])
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def toonJoinRequest(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("toonJoinRequest")
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
inActivity = self.party.isInActivity(senderId)
|
||||
if inActivity:
|
||||
# AI side don't let a toon join multiple activities
|
||||
self.sendToonJoinResponse(senderId, joined=False)
|
||||
return
|
||||
self._playerIds.add(senderId)
|
||||
if self.activityFSM.state == "Idle":
|
||||
if senderId != self.party.partyInfo.hostId:
|
||||
self.air.writeServerEvent('suspicious', senderId, 'non-host trying to start party catch')
|
||||
return
|
||||
self.sendToonJoinResponse(senderId, joined=True)
|
||||
self.activityFSM.request("Active")
|
||||
elif self.activityFSM.state == "Active":
|
||||
# TODO: check against maximum number of players limit
|
||||
self.sendToonJoinResponse(senderId, joined=True)
|
||||
|
||||
def toonExitDemand(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("toonJoinRequest")
|
||||
toonId = self.air.getAvatarIdFromSender()
|
||||
# make sure this toon is currently playing
|
||||
if toonId not in self.toonIds:
|
||||
return
|
||||
if toonId in self.toonIdsToScores:
|
||||
reward = self.toonIdsToScores[toonId]
|
||||
|
||||
# if it's jelly bean day give us more jelly beans!
|
||||
if self.air.holidayManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_DAY):
|
||||
reward *= PartyGlobals.JellyBeanDayMultiplier
|
||||
|
||||
if reward > PartyGlobals.CatchMaxTotalReward:
|
||||
# put a cap so we don't go beyond something ridiculous
|
||||
reward = PartyGlobals.CatchMaxTotalReward
|
||||
self.toonIdsToJellybeanRewards[toonId] = reward
|
||||
self.sendUpdateToAvatarId(toonId, "showJellybeanReward", [
|
||||
reward, self.air.doId2do[toonId].getMoney(),
|
||||
TTLocalizer.PartyCatchRewardMessage %(self.toonIdsToScores[toonId],reward)])
|
||||
del self.toonIdsToScores[toonId]
|
||||
self.toonIdsToJellybeanRewards[toonId] = reward
|
||||
self.issueJellybeanRewardToToonId(toonId)
|
||||
DistributedPartyActivityAI.toonExitDemand(self)
|
||||
if toonId in self._playerIds:
|
||||
self._playerIds.remove(toonId)
|
||||
# number of players changed, start a new generation of drops
|
||||
self._setUpNextGenScheduleTask(globalClock.getRealTime() - self.activityStartTime)
|
||||
|
||||
def sendToonJoinResponse(self, toonId, joined):
|
||||
# since toons can join mid-activity, make sure to add to scores dictionary if needed
|
||||
if joined:
|
||||
if toonId not in self.toonIdsToScores:
|
||||
self.toonIdsToScores[toonId] = 0
|
||||
DistributedPartyActivityAI.sendToonJoinResponse(self, toonId, joined)
|
||||
# number of players changed, start a new generation of drops
|
||||
self._setUpNextGenScheduleTask(globalClock.getRealTime() - self.activityStartTime)
|
||||
|
||||
def _handleUnexpectedToonExit(self, toonId):
|
||||
"""
|
||||
An avatar bailed out because he lost his connection or quit
|
||||
unexpectedly.
|
||||
"""
|
||||
DistributedPartyCatchActivityAI.notify.debug("_handleUnexpectedToonExit( toonId=%s )" % toonId)
|
||||
DistributedPartyActivityAI._handleUnexpectedToonExit(self, toonId)
|
||||
if toonId in self._playerIds:
|
||||
self._playerIds.remove(toonId)
|
||||
if toonId in self.toonIdsToScores:
|
||||
del self.toonIdsToScores[toonId]
|
||||
# number of players changed, start a new generation of drops
|
||||
self._setUpNextGenScheduleTask(globalClock.getRealTime() - self.activityStartTime)
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def claimCatch(self, generation, objNum, DropObjTypeId):
|
||||
if self.activityFSM.state != 'Active':
|
||||
return
|
||||
|
||||
# range check DropObjTypeId
|
||||
if DropObjTypeId < 0 or DropObjTypeId >= len(PartyGlobals.DOTypeId2Name):
|
||||
self.air.writeServerEvent('warning', DropObjTypeId, 'PartyCatchActivityAI.claimCatch DropObjTypeId out of range')
|
||||
return
|
||||
|
||||
gen = self._id2gen.get(generation)
|
||||
if gen is None:
|
||||
# generation doesn't exist yet (?) or has been deprecated
|
||||
self.air.writeServerEvent('warning', generation, 'PartyCatchActivityAI.claimCatch generation is too old or doesn\'t exist yet')
|
||||
return
|
||||
|
||||
# sanity check; don't allow hackers to allocate unlimited memory
|
||||
if objNum < 0 or objNum > 5000 or objNum >= 2*len(gen.caughtList):
|
||||
# DistributedPartyCatchActivityAI.notify.debug('object num %s is too high. ignoring' % objNum)
|
||||
self.air.writeServerEvent('warning', objNum, 'PartyCatchActivityAI.claimCatch objNum is too high or negative')
|
||||
return
|
||||
|
||||
# double the size of the caught table as needed
|
||||
if objNum >= len(gen.caughtList):
|
||||
gen.caughtList += [0] * len(gen.caughtList)
|
||||
|
||||
# if nobody's caught this object yet, announce that it's been caught
|
||||
if not gen.caughtList[objNum]:
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
# make sure this toon is still playing
|
||||
if avId not in self._playerIds:
|
||||
return
|
||||
gen.caughtList[objNum] = 1
|
||||
self.sendUpdate('setObjectCaught', [avId, generation, objNum])
|
||||
# if it's a good obj, update the score
|
||||
objName = PartyGlobals.DOTypeId2Name[DropObjTypeId]
|
||||
DistributedPartyCatchActivityAI.notify.debug('avatar %s caught object %s: %s' %
|
||||
(avId, objNum, objName))
|
||||
if PartyGlobals.Name2DropObjectType[objName].good:
|
||||
self.toonIdsToScores[avId] += 1
|
||||
self.fruitsCaught += 1
|
||||
|
||||
# def reportDone(self):
|
||||
# if self.activityFSM.state != 'Active':
|
||||
# return
|
||||
# avId = self.air.getAvatarIdFromSender()
|
||||
# # all of the objects on this avatar's client have landed
|
||||
# # or been caught
|
||||
# DistributedPartyCatchActivityAI.notify.debug('reportDone: avatar %s is done' % avId)
|
||||
|
||||
# FSM transition methods
|
||||
def startIdle(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("startIdle")
|
||||
self.sendUpdate(
|
||||
"setState",
|
||||
[
|
||||
"Idle", # new state
|
||||
0,
|
||||
]
|
||||
)
|
||||
|
||||
def finishIdle(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("finishIdle")
|
||||
|
||||
def startActive(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("startActive")
|
||||
self.activityStartTime = globalClock.getRealTime()
|
||||
self.sendUpdate('setStartTimestamp', [
|
||||
globalClockDelta.localToNetworkTime(self.activityStartTime, bits=32),])
|
||||
self.sendUpdate(
|
||||
"setState",
|
||||
[
|
||||
"Active", # new state
|
||||
globalClockDelta.localToNetworkTime(self.activityStartTime),
|
||||
]
|
||||
)
|
||||
# populate the scores dictionary
|
||||
self.toonIdsToScores.clear()
|
||||
for toonId in self.toonIds:
|
||||
self.toonIdsToScores[toonId] = 0
|
||||
# and keep track of how many are caught
|
||||
self.fruitsCaught = 0
|
||||
# game is always running
|
||||
#taskMgr.doMethodLater(PartyGlobals.CatchActivityDuration, self.requestState, self.conclusionCountdownTask, ["Conclusion"])
|
||||
self._playerIds = set()
|
||||
# defines self.generationDuration
|
||||
self.calcDifficultyConstants(self.getNumPlayers())
|
||||
self._id2gen = {}
|
||||
self._generation = -1
|
||||
self._lastGenerationStartTime = -self.generationDuration
|
||||
self._schedTasks = {}
|
||||
self._setUpNextGenScheduleTask(0.)
|
||||
|
||||
def requestState(self, state):
|
||||
DistributedPartyCatchActivityAI.notify.debug("requestState : %s" % state)
|
||||
self.activityFSM.request(state)
|
||||
|
||||
def finishActive(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("finishActive")
|
||||
|
||||
def startConclusion(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("startIdle")
|
||||
self.sendUpdate(
|
||||
"setState",
|
||||
[
|
||||
"Conclusion", # new state
|
||||
0,
|
||||
]
|
||||
)
|
||||
|
||||
def finishConclusion(self):
|
||||
DistributedPartyCatchActivityAI.notify.debug("finishIdle")
|
||||
|
||||
def isInActivity(self, toonId):
|
||||
"""Return true if the toon is doing catch."""
|
||||
result = False
|
||||
if toonId in self._playerIds:
|
||||
result = True
|
||||
if result:
|
||||
# lets do a validation check here
|
||||
# they should be in sync
|
||||
if not toonId in self.toonIdsToScores:
|
||||
self.notify.warning("toon %d is in playerIds but not in toonIdsToScores" % toonId)
|
||||
return result
|
||||
|
|
@ -1,5 +1,223 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#===============================================================================
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: September 2009
|
||||
#
|
||||
# Purpose: Distributed controller for Party Cog "Pinata" Activity in the AI
|
||||
#===============================================================================
|
||||
from direct.showbase.PythonUtil import bound as clamp
|
||||
|
||||
class DistributedPartyCogActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCogActivityAI')
|
||||
from toontown.toonbase import TTLocalizer
|
||||
|
||||
from .DistributedPartyTeamActivityAI import DistributedPartyTeamActivityAI
|
||||
from . import PartyGlobals
|
||||
from . import PartyCogUtils
|
||||
|
||||
class DistributedPartyCogActivityAI(DistributedPartyTeamActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyCogActivityAI")
|
||||
|
||||
cogDistances = [0, 0, 0] # [cog0, cog1, cog2] goes from -1.0 to 1.0
|
||||
score = [0, 0] # [leftTeam, rightTeam]
|
||||
toonScore = {}
|
||||
|
||||
highScore = ("", 0) # toonName (in case the toon disconnects), score
|
||||
|
||||
def __init__(self, air, doId, x, y ,h):
|
||||
DistributedPartyTeamActivityAI.__init__(
|
||||
self, air, doId, x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyCog,
|
||||
minPlayersPerTeam=PartyGlobals.CogActivityMinPlayersPerTeam,
|
||||
maxPlayersPerTeam=PartyGlobals.CogActivityMaxPlayersPerTeam,
|
||||
duration=PartyGlobals.CogActivityDuration,
|
||||
conclusionDuration=PartyGlobals.CogActivityConclusionDuration,
|
||||
startDelay=PartyGlobals.CogActivityStartDelay,
|
||||
balanceTeams=True,
|
||||
calcAdvantage=True,
|
||||
canSwitchTeams=True,
|
||||
)
|
||||
|
||||
self.cogHeadStartZ = PartyGlobals.CogPinataHeadZ
|
||||
|
||||
#===============================================================================
|
||||
# Distributed
|
||||
#===============================================================================
|
||||
|
||||
# broadcast clsend airecv
|
||||
def pieHitsCog(self, toonId, timestamp, hitCogNum, x, y, z, direction, hitHead):
|
||||
# Count it only if it's the active state. It may be a very delayed hit, sadly.
|
||||
if not self.isState("Active"):
|
||||
return
|
||||
|
||||
assert(self.notify.debugStateCall(self))
|
||||
|
||||
if hitHead:
|
||||
if z < self.cogHeadStartZ:
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("pieHitsCog suspicious behavior for toon: %s, z: %d < %d" % (toonId, z, self.cogHeadStartZ))
|
||||
self.air.writeServerEvent(
|
||||
"suspicious",
|
||||
senderId,
|
||||
"Toon %s hits cog head in PartyCogActivity, but the head z %d is less than the expected min z %d." %
|
||||
(toonId, z, self.cogHeadStartZ)
|
||||
)
|
||||
|
||||
points = PartyGlobals.CogActivityHitPointsForHead
|
||||
else:
|
||||
points = PartyGlobals.CogActivityHitPoints
|
||||
|
||||
self._addToToonScore(toonId, points)
|
||||
|
||||
advantage = self.advantage[self.getTeam(toonId)]
|
||||
|
||||
self._updateCogDistance(hitCogNum, direction, advantage, hitHead)
|
||||
self.d_broadcastSetCogDistances()
|
||||
|
||||
def d_broadcastSetCogDistances(self):
|
||||
self.sendUpdate("setCogDistances", [self.cogDistances])
|
||||
|
||||
def getHighScore(self):
|
||||
return self.highScore
|
||||
|
||||
# broadcast ram
|
||||
def d_broadcastSetHighScore(self):
|
||||
self.sendUpdate("setHighScore", list(self.highScore))
|
||||
|
||||
#===============================================================================
|
||||
# Actions
|
||||
#===============================================================================
|
||||
|
||||
def _updateCogDistance(self, cogNum, direction, advantage, hitHead):
|
||||
if hitHead:
|
||||
factor = PartyGlobals.CogPinataPushHeadFactor
|
||||
else:
|
||||
factor = PartyGlobals.CogPinataPushBodyFactor
|
||||
|
||||
distance = self.cogDistances[cogNum] + (factor * direction * advantage)
|
||||
|
||||
self.cogDistances[cogNum] = clamp(distance, -1.0, 1.0)
|
||||
|
||||
def _resetCogDistances(self):
|
||||
self.cogDistances = [0, 0, 0]
|
||||
|
||||
def _resetScores(self):
|
||||
self.toonScore.clear()
|
||||
self.score = [0, 0]
|
||||
|
||||
def _addToToonScore(self, toonId, points):
|
||||
if toonId in self.toonScore:
|
||||
self.toonScore[toonId] += points
|
||||
|
||||
def _addToonToTeam(self, toonId, team):
|
||||
if DistributedPartyTeamActivityAI._addToonToTeam(self, toonId, team):
|
||||
self.toonScore[toonId] = 0
|
||||
|
||||
def _removeToonFromTeam(self, toonId, team):
|
||||
if DistributedPartyTeamActivityAI._removeToonFromTeam(self, toonId, team) and \
|
||||
toonId in self.toonScore:
|
||||
del self.toonScore[toonId]
|
||||
|
||||
def _findNewHighScore(self):
|
||||
"""Check to see if a new high score has been made, and broadcast"""
|
||||
highScoreFound = False
|
||||
|
||||
for toonId, score in list(self.toonScore.items()):
|
||||
if score > self.highScore[1] and toonId in self.air.doId2do:
|
||||
self.highScore = (self.air.doId2do[toonId].getName(), score)
|
||||
highScoreFound = True
|
||||
|
||||
if highScoreFound:
|
||||
self.d_broadcastSetHighScore()
|
||||
|
||||
#===============================================================================
|
||||
# Utility methods
|
||||
#===============================================================================
|
||||
|
||||
def computeMatchResults(self):
|
||||
"""Determines who was the winning team and rewards the teams accordingly."""
|
||||
# Determine how far the cogs were pushed for every team.
|
||||
for distance in self.cogDistances:
|
||||
if distance < 0.0:
|
||||
self.score[PartyGlobals.TeamActivityTeams.LeftTeam] += abs(PartyCogUtils.getCogDistanceUnitsFromCenter(distance))
|
||||
elif distance > 0.0:
|
||||
self.score[PartyGlobals.TeamActivityTeams.RightTeam] += abs(PartyCogUtils.getCogDistanceUnitsFromCenter(distance))
|
||||
|
||||
# Determine who won, who lost:
|
||||
if self.score[PartyGlobals.TeamActivityTeams.LeftTeam] < self.score[PartyGlobals.TeamActivityTeams.RightTeam]:
|
||||
self.losingTeam = PartyGlobals.TeamActivityTeams.LeftTeam
|
||||
elif self.score[PartyGlobals.TeamActivityTeams.RightTeam] < self.score[PartyGlobals.TeamActivityTeams.LeftTeam]:
|
||||
self.losingTeam = PartyGlobals.TeamActivityTeams.RightTeam
|
||||
else:
|
||||
self.losingTeam = PartyGlobals.TeamActivityNeitherTeam
|
||||
self.winningTeam = PartyGlobals.TeamActivityNeitherTeam
|
||||
|
||||
# Tie:
|
||||
if self.resultIsTie():
|
||||
|
||||
for toonId in self.getToonIdsAsList():
|
||||
self.toonIdsToJellybeanRewards[toonId] = PartyGlobals.CogActivityTieBeans
|
||||
|
||||
# One of the teams won:
|
||||
else:
|
||||
self.winningTeam = 1 - self.losingTeam
|
||||
|
||||
winBeans = PartyGlobals.CogActivityWinBeans
|
||||
lossBeans = PartyGlobals.CogActivityLossBeans
|
||||
|
||||
# The winning team moved all of the cogs all the way:
|
||||
if self.resultIsPerfect():
|
||||
winBeans = PartyGlobals.CogActivityPerfectWinBeans
|
||||
lossBeans = PartyGlobals.CogActivityPerfectLossBeans
|
||||
|
||||
for toonId in self.toonIds[self.winningTeam]:
|
||||
self.toonIdsToJellybeanRewards[toonId] = winBeans
|
||||
|
||||
for toonId in self.toonIds[self.losingTeam]:
|
||||
self.toonIdsToJellybeanRewards[toonId] = lossBeans
|
||||
|
||||
self._findNewHighScore()
|
||||
|
||||
def getConclusionData(self):
|
||||
"""Formats the score into a single 32 bit data"""
|
||||
return (int(self.score[0]) * 10000 + int(self.score[1]))
|
||||
|
||||
def getJellybeanRewardMessage(self, toonId, reward):
|
||||
message = None
|
||||
|
||||
if self.resultIsTie():
|
||||
message = TTLocalizer.PartyTeamActivityRewardMessage % reward
|
||||
elif self.toonIsOnWinningTeam(toonId):
|
||||
# "Your team won!\n\nYou got %d jellybeans. Good job!"
|
||||
message = TTLocalizer.PartyTeamActivityLocalAvatarTeamWins + "\n\n" +\
|
||||
TTLocalizer.PartyTeamActivityRewardMessage % reward
|
||||
else:
|
||||
message = TTLocalizer.PartyTeamActivityRewardMessage % reward
|
||||
|
||||
return message
|
||||
|
||||
def resultIsTie(self):
|
||||
""" Returns True if the teams tied. """
|
||||
return self.losingTeam == PartyGlobals.TeamActivityNeitherTeam
|
||||
|
||||
def resultIsPerfect(self):
|
||||
""" Returns True if the winning team moved all of the cogs all the way. """
|
||||
return self.score[self.winningTeam] >= 3.0 * PartyGlobals.CogActivityArenaLength / 2.0
|
||||
|
||||
def toonIsOnWinningTeam(self, toonId):
|
||||
""" Returns True if the given toon was on the winning team. """
|
||||
return toonId in self.toonIds[self.winningTeam]
|
||||
|
||||
#===============================================================================
|
||||
# FSM
|
||||
#===============================================================================
|
||||
|
||||
def startConclusion(self):
|
||||
# All clients should first and foremost, synchronize cog distances.
|
||||
self.d_broadcastSetCogDistances()
|
||||
|
||||
DistributedPartyTeamActivityAI.startConclusion(self)
|
||||
|
||||
def startWaitForEnough(self):
|
||||
DistributedPartyTeamActivityAI.startWaitForEnough(self)
|
||||
|
||||
self._resetScores()
|
||||
self._resetCogDistances()
|
||||
self.d_broadcastSetCogDistances()
|
||||
|
|
@ -1,5 +1,24 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyDance20ActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyDance20ActivityAI')
|
||||
from toontown.parties.DistributedPartyDanceActivityBaseAI import DistributedPartyDanceActivityBaseAI
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
# 20 move dance floor
|
||||
class DistributedPartyDance20ActivityAI(DistributedPartyDanceActivityBaseAI):
|
||||
notify = directNotify.newCategory("DistributedPartyDanceActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyDanceActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyDance20,
|
||||
PartyGlobals.DancePatternToAnims20,
|
||||
)
|
||||
|
|
@ -1,5 +1,24 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyDanceActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyDanceActivityAI')
|
||||
from toontown.parties.DistributedPartyDanceActivityBaseAI import DistributedPartyDanceActivityBaseAI
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
# original classic 10 move dance floor
|
||||
class DistributedPartyDanceActivityAI(DistributedPartyDanceActivityBaseAI):
|
||||
notify = directNotify.newCategory("DistributedPartyDanceActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyDanceActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyDance,
|
||||
PartyGlobals.DancePatternToAnims,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -326,14 +326,16 @@ class DistributedPartyDanceActivityBase(DistributedPartyActivity):
|
|||
|
||||
def _requestToonState(self, toonId, state, anim):
|
||||
if toonId in self.dancingToonFSMs:
|
||||
state = ToonDancingStates.getString(state)
|
||||
state = ToonDancingStates(state)
|
||||
curState = self.dancingToonFSMs[toonId].getCurrentOrNextState()
|
||||
try:
|
||||
state = str(state)
|
||||
anim = str(anim)
|
||||
self.dancingToonFSMs[toonId].request(state, anim)
|
||||
except FSM.RequestDenied:
|
||||
self.notify.warning('could not go from state=%s to state %s' % (curState, state))
|
||||
|
||||
if state == ToonDancingStates.getString(ToonDancingStates.Cleanup):
|
||||
if state == ToonDancingStates(ToonDancingStates.Cleanup):
|
||||
self.notify.debug('deleting this fsm %s' % self.dancingToonFSMs[toonId])
|
||||
del self.dancingToonFSMs[toonId]
|
||||
if self.localToonDanceSequence:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,124 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
from toontown.parties.DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyDanceActivityBaseAI(DistributedPartyActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyDanceActivityBaseAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h, activityId, dancePatternToAnims):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyActivityAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
activityId,
|
||||
PartyGlobals.ActivityTypes.Continuous)
|
||||
self.toonIdsToHeadings = {} # toon's heading when it joined
|
||||
self.dancePatternToAnims = dancePatternToAnims
|
||||
|
||||
def delete(self):
|
||||
pass
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def toonJoinRequest(self):
|
||||
"""Gets from client when a toon enters the dance floor."""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("Request enter %s" % senderId)
|
||||
if senderId not in self.toonIds:
|
||||
if self.party.isInActivity(senderId):
|
||||
hToUse = 0
|
||||
toon = self.air.doId2do.get(senderId)
|
||||
if toon:
|
||||
hToUse = toon.getH()
|
||||
self.sendToonJoinResponse(senderId,
|
||||
joined = False,
|
||||
h = hToUse)
|
||||
else:
|
||||
self.sendToonJoinResponse(senderId,
|
||||
joined = True,
|
||||
h = self.air.doId2do[senderId].getH())
|
||||
|
||||
else:
|
||||
self.air.writeServerEvent(
|
||||
"suspicious",
|
||||
senderId,
|
||||
"Party Dance Activity AI Join Request: Sender already in Party Dance Activity"
|
||||
)
|
||||
self.notify.warning("toonJoinRequest() - Sender already in Activity")
|
||||
|
||||
def sendToonJoinResponse(self, toonId, joined=True, h=0.0):
|
||||
if joined:
|
||||
self._addToon(toonId)
|
||||
self.toonIdsToHeadings[toonId] = h
|
||||
self.notify.debug("sending setToonsPlaying")
|
||||
self.notify.debug(" toonIds: %s" %self.toonIds)
|
||||
self.notify.debug(" toonHeadings: %s" %self.getHeadingList())
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds, self.getHeadingList()])
|
||||
else:
|
||||
self.sendUpdateToAvatarId(toonId, "joinRequestDenied", [PartyGlobals.DenialReasons.SilentFail])
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def toonExitRequest(self):
|
||||
"""
|
||||
Gets from client when a dancing toon enters the dance floor.
|
||||
"""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("Request exit %s" % senderId)
|
||||
if senderId in self.toonIds:
|
||||
self.sendToonExitResponse(senderId, exited = True)
|
||||
else:
|
||||
# this case is possible when a toon lands on dance floor from the cannon
|
||||
# joinRequest is never sent by client but exitRequest is sent
|
||||
self.sendToonExitResponse(senderId, exited = False, denialReason=PartyGlobals.DenialReasons.SilentFail)
|
||||
|
||||
|
||||
def sendToonExitResponse(self, toonId, exited, denialReason=PartyGlobals.DenialReasons.Default):
|
||||
if exited:
|
||||
self._removeToon(toonId)
|
||||
del self.toonIdsToHeadings[toonId]
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds, self.getHeadingList()])
|
||||
else:
|
||||
self.sendUpdateToAvatarId(toonId, "exitRequestDenied", [denialReason])
|
||||
|
||||
def getHeadingList(self):
|
||||
"""
|
||||
Returns a list of initial headings for toons in the activity, in the
|
||||
same order as self.toonIds
|
||||
"""
|
||||
headingList = []
|
||||
for toonId in self.toonIds:
|
||||
headingList.append(self.toonIdsToHeadings[toonId])
|
||||
return headingList
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def updateDancingToon(self, state, anim):
|
||||
"""
|
||||
Gets from client when a dancing toon performs a dance move.
|
||||
"""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if anim != "" and anim not in list(self.dancePatternToAnims.values()):
|
||||
# TODO: Log suspicious behavior?
|
||||
return
|
||||
self.d_setDancingToonState(senderId, state, anim)
|
||||
self.notify.debug("Request dance move %s" % senderId)
|
||||
|
||||
# Distributed (broadcast)
|
||||
def d_setDancingToonState(self, toonId, state, anim):
|
||||
self.sendUpdate("setDancingToonState", [toonId, state, anim])
|
||||
|
||||
def _handleUnexpectedToonExit(self, toonId):
|
||||
"""
|
||||
An avatar bailed out because he lost his connection or quit
|
||||
unexpectedly. Overriding base class functionality because of the special
|
||||
setToonsPlaying function in this class.
|
||||
"""
|
||||
self._removeToon(toonId)
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds, self.getHeadingList()])
|
||||
|
||||
class DistributedPartyDanceActivityBaseAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyDanceActivityBaseAI')
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DistributedPartyFireworksActivity(DistributedPartyActivity, FireworkShowMi
|
|||
FireworkShowMixin.__init__(self, restorePlaygroundMusic=True, startDelay=FireworksPostLaunchDelay)
|
||||
|
||||
def setEventId(self, eventId):
|
||||
DistributedPartyFireworksActivity.notify.debug('setEventId( %s )' % FireworkShows.getString(eventId))
|
||||
DistributedPartyFireworksActivity.notify.debug('setEventId( %s )' % FireworkShows(eventId))
|
||||
self.eventId = eventId
|
||||
|
||||
def setShowStyle(self, showStyle):
|
||||
|
|
@ -56,7 +56,7 @@ class DistributedPartyFireworksActivity(DistributedPartyActivity, FireworkShowMi
|
|||
self.rocketActor = Actor('phase_13/models/parties/rocket_model', {'launch': 'phase_13/models/parties/rocket_launch'})
|
||||
rocketLocator = self.launchPadModel.find('**/rocket_locator')
|
||||
self.rocketActor.reparentTo(rocketLocator)
|
||||
self.rocketActor.node().setBound(OmniBoundingVolume())
|
||||
self.rocketActor.node().setBounds(OmniBoundingVolume())
|
||||
self.rocketActor.node().setFinal(True)
|
||||
effectsLocator = self.rocketActor.find('**/joint1')
|
||||
self.rocketExplosionEffect = RocketExplosion(effectsLocator, rocketLocator)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,131 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Rob Gordon
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI control of fireworks in a party.
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyFireworksActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyFireworksActivityAI')
|
||||
# Panda imports
|
||||
from direct.distributed import ClockDelta
|
||||
from direct.task import Task
|
||||
|
||||
# Toontown imports
|
||||
from toontown.effects.FireworkShow import FireworkShow
|
||||
|
||||
# parties imports
|
||||
from . import PartyGlobals
|
||||
from .DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from .activityFSMs import FireworksActivityFSM
|
||||
|
||||
class DistributedPartyFireworksActivityAI(DistributedPartyActivityAI):
|
||||
|
||||
notify = directNotify.newCategory("DistributedPartyFireworksActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h, eventId=PartyGlobals.FireworkShows.Summer, showStyle=0):
|
||||
"""
|
||||
air: instance of ToontownAIRepository
|
||||
eventId: a PartyGlobals.FireworkShows value that tells us which show
|
||||
to run
|
||||
"""
|
||||
DistributedPartyFireworksActivityAI.notify.debug("__init__")
|
||||
DistributedPartyActivityAI.__init__(
|
||||
self,
|
||||
air,
|
||||
partyDoId,
|
||||
x,
|
||||
y,
|
||||
h,
|
||||
PartyGlobals.ActivityIds.PartyFireworks,
|
||||
PartyGlobals.ActivityTypes.HostInitiated,
|
||||
)
|
||||
self.eventId = eventId
|
||||
self.showStyle = showStyle
|
||||
self.activityFSM = FireworksActivityFSM(self)
|
||||
|
||||
def generate(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("generate")
|
||||
self.activityFSM.request("Idle")
|
||||
|
||||
def getEventId(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("getEventId")
|
||||
return self.eventId
|
||||
|
||||
def getShowStyle(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("getShowStyle")
|
||||
return self.showStyle
|
||||
|
||||
def toonJoinRequest(self):
|
||||
"""
|
||||
The supposed host is requesting the fireworks to start.
|
||||
"""
|
||||
# check that the host sent this message
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if senderId != self.party.partyInfo.hostId:
|
||||
self.air.writeServerEvent('suspicious', senderId, 'A non-host with avId=%d tried to start a host activated activity.' % senderId)
|
||||
return
|
||||
self.activityFSM.request("Active")
|
||||
|
||||
def showComplete(self, task):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("showComplete")
|
||||
self.activityFSM.request("Disabled")
|
||||
|
||||
return Task.done
|
||||
|
||||
def delete(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("delete")
|
||||
if hasattr(self, 'activityFSM'):
|
||||
self.activityFSM.request('Disabled')
|
||||
del self.activityFSM
|
||||
DistributedPartyActivityAI.delete(self)
|
||||
|
||||
# FSM transition methods
|
||||
def startIdle(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("startIdle")
|
||||
|
||||
def finishIdle(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("finishIdle")
|
||||
|
||||
def startActive(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("startActive")
|
||||
messenger.send( PartyGlobals.FireworksStartedEvent )
|
||||
showStartTimestamp = ClockDelta.globalClockDelta.getRealNetworkTime()
|
||||
# put clients into this state
|
||||
self.sendUpdate(
|
||||
"setState",
|
||||
[
|
||||
"Active", # new state
|
||||
showStartTimestamp # when the show starts
|
||||
]
|
||||
)
|
||||
# setup to transition to Disabled after the show is over
|
||||
throwAwayShow = FireworkShow()
|
||||
showDuration = throwAwayShow.getShowDuration( self.eventId)
|
||||
showDuration += 20.0
|
||||
taskMgr.doMethodLater(
|
||||
PartyGlobals.FireworksPostLaunchDelay + showDuration + PartyGlobals.FireworksTransitionToDisabledDelay,
|
||||
self.showComplete,
|
||||
self.taskName("waitForShowComplete"),
|
||||
)
|
||||
del throwAwayShow
|
||||
|
||||
def finishActive(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("finishActive")
|
||||
messenger.send( PartyGlobals.FireworksFinishedEvent )
|
||||
# clean up doMethodLater
|
||||
taskMgr.removeTasksMatching(self.taskName("waitForShowComplete"))
|
||||
|
||||
def startDisabled(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("startDisabled")
|
||||
# put clients into this state
|
||||
self.sendUpdate(
|
||||
"setState",
|
||||
[
|
||||
"Disabled", # new state
|
||||
0 # dummy timestamp
|
||||
]
|
||||
)
|
||||
|
||||
def finishDisabled(self):
|
||||
DistributedPartyFireworksActivityAI.notify.debug("finishDisabled")
|
||||
|
||||
|
|
@ -1,5 +1,25 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyJukebox40ActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyJukebox40ActivityAI')
|
||||
from toontown.parties.DistributedPartyJukeboxActivityBaseAI import DistributedPartyJukeboxActivityBaseAI
|
||||
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyJukebox40ActivityAI(DistributedPartyJukeboxActivityBaseAI ):
|
||||
notify = directNotify.newCategory("DistributedPartyJukeboxActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyJukeboxActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyJukebox40,
|
||||
PartyGlobals.PhaseToMusicData40,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,25 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyJukeboxActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyJukeboxActivityAI')
|
||||
from toontown.parties.DistributedPartyJukeboxActivityBaseAI import DistributedPartyJukeboxActivityBaseAI
|
||||
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyJukeboxActivityAI(DistributedPartyJukeboxActivityBaseAI ):
|
||||
notify = directNotify.newCategory("DistributedPartyJukeboxActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyJukeboxActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyJukebox,
|
||||
PartyGlobals.PhaseToMusicData,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,285 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
#-------------------------------------------------------------------------------
|
||||
import random
|
||||
from direct.task.Task import Task
|
||||
|
||||
from toontown.parties.DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from toontown.parties import PartyGlobals
|
||||
from random import randint
|
||||
|
||||
class DistributedPartyJukeboxActivityBaseAI(DistributedPartyActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyJukeboxActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h, actId, phaseToMusicData):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyActivityAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
actId,
|
||||
PartyGlobals.ActivityTypes.Continuous
|
||||
)
|
||||
|
||||
# Holds the list of songs requested by each avatar.
|
||||
# Format: { toonId : (phase, filename), ... }
|
||||
self.phaseToMusicData = phaseToMusicData
|
||||
self.toonIdToSongData = {}
|
||||
# Holds the queue of songs to be played by avatarId
|
||||
self.toonIdQueue = []
|
||||
|
||||
self.songPlaying = False
|
||||
self.songTimerTask = None
|
||||
|
||||
self.timeoutTask = None
|
||||
|
||||
# listen for fireworks show starting and stopping
|
||||
self.accept( PartyGlobals.FireworksStartedEvent, self.__handleFireworksStarted )
|
||||
self.accept( PartyGlobals.FireworksFinishedEvent, self.__handleFireworksFinished )
|
||||
|
||||
# this ensure the first song we hear is one of the new ones
|
||||
self.randomSongPhase = 13
|
||||
|
||||
def delete(self):
|
||||
self.ignoreAll()
|
||||
self.__stopTimeout()
|
||||
DistributedPartyActivityAI.delete(self)
|
||||
|
||||
def generate(self):
|
||||
DistributedPartyActivityAI.generate(self)
|
||||
self.__playNextSong()
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def toonJoinRequest(self):
|
||||
"""
|
||||
Gets from client when a toon requests to use the jukebox.
|
||||
only one toon can use the jukebox.
|
||||
"""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("Request enter %s" % senderId)
|
||||
if senderId not in self.toonIds:
|
||||
joined = (len(self.toonIds) == 0)
|
||||
if self.party.isInActivity(senderId):
|
||||
joined = False
|
||||
self.__startTimeout(PartyGlobals.JUKEBOX_TIMEOUT)
|
||||
self.sendToonJoinResponse(senderId, joined)
|
||||
#TODO: Add suspicious behavior case?
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def toonExitRequest(self):
|
||||
"""
|
||||
Sent from client when toon using the jukebox wishes to stop using the
|
||||
jukebox.
|
||||
"""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("Request exit %s" % senderId)
|
||||
if senderId in self.toonIds:
|
||||
self.sendToonExitResponse(senderId, True)
|
||||
else:
|
||||
self.sendToonExitResponse(senderId, False)
|
||||
|
||||
def toonExitResponse(self, senderId, exited):
|
||||
if exited:
|
||||
self.__stopTimeout()
|
||||
DistributedPartyActivityAI.sendToonExitResponse(self, senderId, exited)
|
||||
|
||||
#===============================================================================
|
||||
# Song addition, playing, etc.
|
||||
#===============================================================================
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def queuedSongsRequest(self):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
|
||||
songInfoList = []
|
||||
index = -1
|
||||
for i in range(len(self.toonIdQueue)):
|
||||
toonId = self.toonIdQueue[i]
|
||||
if toonId == senderId:
|
||||
index = i
|
||||
songInfoList.append(self.toonIdToSongData[toonId])
|
||||
self.d_queuedSongsResponse(senderId, songInfoList, index)
|
||||
|
||||
# Distributed
|
||||
def d_queuedSongsResponse(self, senderId, songInfoList, index):
|
||||
self.sendUpdateToAvatarId(senderId,
|
||||
"queuedSongsResponse",
|
||||
[songInfoList, index])
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def setNextSong(self, nextSongInfo):
|
||||
"""
|
||||
Called by client when adding or replacing a song in the queue.
|
||||
"""
|
||||
phase = PartyGlobals.sanitizePhase(nextSongInfo[0])
|
||||
filename = nextSongInfo[1]
|
||||
self.notify.debug("setNextSong %d/%s" % (phase, filename))
|
||||
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
data = self.getMusicData(phase, filename)
|
||||
if data:
|
||||
self.toonIdToSongData[senderId] = (phase, filename)
|
||||
if senderId not in self.toonIdQueue:
|
||||
self.toonIdQueue.append(senderId)
|
||||
self.d_setSongInQueue(senderId, nextSongInfo)
|
||||
|
||||
# Distributed (required broadcast ram)
|
||||
def d_setSongPlaying(self, phase, filename, toonId=0):
|
||||
self.sendUpdate("setSongPlaying", [(phase, filename), toonId])
|
||||
|
||||
# Distributed
|
||||
def d_setSongInQueue(self, toonId, songInfo):
|
||||
self.sendUpdateToAvatarId(toonId, "setSongInQueue", [songInfo])
|
||||
|
||||
#===============================================================================
|
||||
# Host Toon only
|
||||
#===============================================================================
|
||||
|
||||
# Distributed (clsend airecv)
|
||||
def moveHostSongToTopRequest(self):
|
||||
self.notify.debug("moveHostSongToTopRequest")
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
# TODO: Check to see if senderId is in fact party host
|
||||
if senderId in self.toonIdQueue:
|
||||
self.b_moveHostSongToTop(senderId)
|
||||
|
||||
def moveHostSongToTop(self, hostId):
|
||||
self.toonIdQueue.remove(hostId)
|
||||
self.toonIdQueue.insert(0, hostId)
|
||||
|
||||
def d_moveHostSongToTop(self, hostId):
|
||||
self.notify.debug("d_moveHostSongToTop")
|
||||
self.sendUpdateToAvatarId(hostId, "moveHostSongToTop", [])
|
||||
|
||||
def b_moveHostSongToTop(self, hostId):
|
||||
self.moveHostSongToTop(hostId)
|
||||
self.d_moveHostSongToTop(hostId)
|
||||
|
||||
#===============================================================================
|
||||
# Song scheduling
|
||||
#===============================================================================
|
||||
|
||||
def __playNextSong(self):
|
||||
self.notify.debug("__playNextSong")
|
||||
"""
|
||||
Pops song out of the queue, broadcasts new song to all clients,
|
||||
and schedules next play task.
|
||||
"""
|
||||
self.songPlaying = False
|
||||
toonId = 0
|
||||
# Play the next song in the queue
|
||||
if len(self.toonIdQueue) > 0:
|
||||
toonId = self.toonIdQueue[0]
|
||||
songInfo = self.toonIdToSongData.get(toonId, None)
|
||||
del self.toonIdToSongData[toonId]
|
||||
self.toonIdQueue.remove(toonId)
|
||||
|
||||
# No song in the queue? Pick a random song!
|
||||
else:
|
||||
songInfo = self.getRandomMusicInfo(self.randomSongPhase)
|
||||
# every other random song is one of the new party songs
|
||||
self.randomSongPhase = -1
|
||||
self.notify.debug("random songInfo=%s" % str(songInfo))
|
||||
|
||||
if songInfo is not None:
|
||||
phase = songInfo[0]
|
||||
filename = songInfo[1]
|
||||
data = self.getMusicData(phase, filename)
|
||||
if data:
|
||||
self.notify.debug("Playing song: %s, %s" % (phase, filename))
|
||||
self.d_setSongPlaying(phase, filename, toonId)
|
||||
self.__startTimerForNextSong(self.__getTimeUntilNextSong(data[1]))
|
||||
self.songPlaying = True
|
||||
|
||||
def __getTimeUntilNextSong(self, duration):
|
||||
return (PartyGlobals.getMusicRepeatTimes(duration) * duration) + PartyGlobals.MUSIC_GAP
|
||||
|
||||
def __playNextSongTask(self, task):
|
||||
self.__playNextSong()
|
||||
return Task.done
|
||||
|
||||
def __startTimerForNextSong(self, seconds):
|
||||
self.notify.debug("Playing next song in %d seconds." % seconds)
|
||||
self.__stopTimerForNextSong()
|
||||
self.songTimerTask = taskMgr.doMethodLater(seconds,
|
||||
self.__playNextSongTask,
|
||||
self.taskName("playNextSong"))
|
||||
|
||||
def __stopTimerForNextSong(self):
|
||||
if self.songTimerTask != None:
|
||||
taskMgr.remove(self.songTimerTask)
|
||||
self.songTimerTask = None
|
||||
|
||||
#===============================================================================
|
||||
# Timeout
|
||||
#===============================================================================
|
||||
|
||||
def __startTimeout(self, timeLimit):
|
||||
"""
|
||||
Sets the timeout counter running. If __stopTimeout() is not
|
||||
called before the time expires, we'll exit the avatar. This
|
||||
prevents avatars from hanging out in the jukebox all day.
|
||||
"""
|
||||
self.__stopTimeout()
|
||||
self.timeoutTask = taskMgr.doMethodLater(timeLimit,
|
||||
self.__handleTimeoutTask,
|
||||
self.taskName("timeout"))
|
||||
|
||||
def __stopTimeout(self):
|
||||
"""
|
||||
Stops a previously-set timeout from expiring.
|
||||
"""
|
||||
if self.timeoutTask != None:
|
||||
taskMgr.remove(self.timeoutTask)
|
||||
self.timeoutTask = None
|
||||
|
||||
def __handleTimeoutTask(self, task):
|
||||
"""
|
||||
Called when a timeout expires, this kicks the toon out of the jukebox.
|
||||
"""
|
||||
self.notify.debug('Timeout expired!')
|
||||
if len(self.toonIds) > 0:
|
||||
self.toonExitResponse(self.toonIds[0], True)
|
||||
return Task.done
|
||||
|
||||
|
||||
def getRandomMusicInfo(self, phase=13):
|
||||
if phase == -1:
|
||||
# Get random phase
|
||||
keys = list(self.phaseToMusicData.keys())
|
||||
# bias random music towards the new party songs
|
||||
keys += [13,13,13,]
|
||||
phase = random.choice(keys) # this is random.choice
|
||||
|
||||
# From that phase, get random filename
|
||||
values = list(self.phaseToMusicData[phase].keys())
|
||||
filename = values[randint(0, len(values) - 1)]
|
||||
|
||||
return (phase, filename)
|
||||
|
||||
|
||||
def getMusicData(self, phase, filename):
|
||||
data = []
|
||||
phase = PartyGlobals.sanitizePhase(phase)
|
||||
phase = self.phaseToMusicData.get(phase)
|
||||
if phase:
|
||||
data = phase.get(filename, [])
|
||||
return data
|
||||
|
||||
#===============================================================================
|
||||
# React to fireworks show
|
||||
#===============================================================================
|
||||
|
||||
def __handleFireworksStarted(self):
|
||||
self.notify.debug("__handleFireworksStarted")
|
||||
self.__stopTimerForNextSong()
|
||||
self.d_setSongPlaying(0, "", 0)
|
||||
|
||||
def __handleFireworksFinished(self):
|
||||
self.notify.debug("__handleFireworksFinished")
|
||||
self.__playNextSong()
|
||||
|
||||
class DistributedPartyJukeboxActivityBaseAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyJukeboxActivityBaseAI')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,830 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#===============================================================================
|
||||
# Contact: Rob Gordon, Edmundo Ruiz (Schell Games)
|
||||
# Created: October 14, 2009
|
||||
# Purpose: Base class for a session-based activity with two teams (AI side).
|
||||
#
|
||||
# Note that this class was originally extracted from
|
||||
# DistributedPartyTugOfWarActivityAI.py
|
||||
#===============================================================================
|
||||
# Config Overrides:
|
||||
# party-team-activity-single-player bool
|
||||
# Allows the dev to run the activity with a single toon playing in it
|
||||
#
|
||||
# party-team-activity-start-delay int
|
||||
# How long the game should wait before it starts
|
||||
#
|
||||
# party-team-activity-duration int
|
||||
# How long the Active states lasts in the game
|
||||
#===============================================================================
|
||||
from direct.distributed import ClockDelta
|
||||
|
||||
class DistributedPartyTeamActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyTeamActivityAI')
|
||||
from toontown.toonbase import TTLocalizer
|
||||
|
||||
from toontown.parties.DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from toontown.parties.activityFSMs import TeamActivityAIFSM
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyTeamActivityAI(DistributedPartyActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyTeamActivityAI")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
air, partyDoId, x, y, h,
|
||||
activityId,
|
||||
minPlayersPerTeam = PartyGlobals.TeamActivityDefaultMinPlayersPerTeam,
|
||||
maxPlayersPerTeam = PartyGlobals.TeamActivityDefaultMaxPlayersPerTeam,
|
||||
duration = PartyGlobals.TeamActivityDefaultDuration,
|
||||
conclusionDuration = PartyGlobals.TeamActivityDefaultConclusionDuration,
|
||||
startDelay = PartyGlobals.TeamActivityStartDelay,
|
||||
balanceTeams = False,
|
||||
calcAdvantage = False,
|
||||
canSwitchTeams = False,
|
||||
):
|
||||
DistributedPartyActivityAI.__init__(
|
||||
self,
|
||||
air,
|
||||
partyDoId,
|
||||
x,
|
||||
y,
|
||||
h,
|
||||
activityId,
|
||||
PartyGlobals.ActivityTypes.GuestInitiated,
|
||||
)
|
||||
self.notify.debug("__init__")
|
||||
|
||||
self._minPlayersPerTeam = minPlayersPerTeam
|
||||
self._maxPlayersPerTeam = maxPlayersPerTeam
|
||||
|
||||
# How long should the conclusion state last in seconds?
|
||||
self._conclusionDuration = conclusionDuration
|
||||
|
||||
# How long the active state lasts
|
||||
self._duration = simbase.config.GetFloat("party-team-activity-duration", duration)
|
||||
|
||||
# How long should it wait for more players before going to the active state
|
||||
self._startDelay = simbase.config.GetFloat("party-team-activity-start-delay", startDelay)
|
||||
|
||||
# Should it calculate whether both team have roughly an equal amount of players?
|
||||
self._shouldBalanceTeams = balanceTeams
|
||||
|
||||
# Should it calculate the advantage of a smaller team over a larger team?
|
||||
self._shouldCalcAdvantage = calcAdvantage
|
||||
|
||||
# Can players willingly switch teams before the game starts?
|
||||
self._canSwitchTeams = canSwitchTeams
|
||||
|
||||
# Do we allow a single player? (This is for testing purposes)
|
||||
self._allowSinglePlayer = simbase.config.GetBool("party-team-activity-single-player", False)
|
||||
|
||||
self.toonIds = (
|
||||
[], # doIds of toons on the left team, ordered from those nearest the water to those furthest
|
||||
[], # doIds of toons on the right team, ordered from those nearest the water to those furthest
|
||||
)
|
||||
|
||||
self.readyToonIds = set()
|
||||
|
||||
# Keep track of which side each avatar is on for quick look up during game
|
||||
self.toonIdsToTeams = {}
|
||||
|
||||
# state transition request doLater
|
||||
self._srTask = None
|
||||
|
||||
self.activityFSM = TeamActivityAIFSM(self)
|
||||
|
||||
def generate(self):
|
||||
DistributedPartyActivityAI.generate(self)
|
||||
self.notify.debug("generate")
|
||||
|
||||
self.activityFSM.request("WaitForEnough")
|
||||
|
||||
def announceGenerate(self):
|
||||
DistributedPartyActivityAI.announceGenerate(self)
|
||||
self.notify.debug("announceGenerate")
|
||||
|
||||
def delete(self):
|
||||
self.cleanupRequestLater()
|
||||
|
||||
DistributedPartyActivityAI.delete(self)
|
||||
|
||||
#===============================================================================
|
||||
# Actions
|
||||
#===============================================================================
|
||||
|
||||
def _addToonToTeam(self, toonId, team):
|
||||
"""
|
||||
Checks and adds a toon to the team
|
||||
|
||||
Parameters
|
||||
toonId toon's doId
|
||||
team team number based on PartyGlobals.TeamActivityTeams
|
||||
|
||||
Returns
|
||||
true if it successfully added the toon to the team
|
||||
"""
|
||||
self.notify.debug("_addToonToTeam")
|
||||
|
||||
# check team size limits
|
||||
if self.isTeamFull(team):
|
||||
self.notify.error("Tried to add toonId=%s to %s when it was already full." % (toonId, self.getTeamName(team)))
|
||||
# check that toonId isn't already in that team's list
|
||||
elif toonId in self.toonIds[team]:
|
||||
self.notify.warning("Tried to add toonId=%s to %s when it was already on that team. Ignoring request." % (toonId, self.getTeamName(team)))
|
||||
else:
|
||||
self.toonIds[team].append(toonId)
|
||||
self.toonIdsToTeams[toonId] = team
|
||||
self.toonId2joinTime[toonId] = globalClock.getFrameTime()
|
||||
|
||||
# listen for this avatar's exit event
|
||||
self.acceptOnce(
|
||||
self.air.getAvatarExitEvent(toonId),
|
||||
self._handleUnexpectedToonExit,
|
||||
extraArgs=[toonId],
|
||||
)
|
||||
|
||||
self.notify.debug("Added toonId=%s to %s." % (toonId, self.getTeamName(team)))
|
||||
self.notify.debug("toonIds:\n\t\t%s\n\t\t%s." % (self.toonIds[0], self.toonIds[1]))
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _removeToonFromTeam(self, toonId, team):
|
||||
"""
|
||||
Checks and removes a toon from a team
|
||||
|
||||
Parameters
|
||||
toonId toon's doId
|
||||
team team number based on PartyGlobals.TeamActivityTeams
|
||||
|
||||
Returns
|
||||
true if it successfully removed the toon from the team
|
||||
"""
|
||||
self.notify.debug("_removeToonFromTeam")
|
||||
|
||||
if type(team) != type(1):
|
||||
self.notify.warning("Tried to remove toonId=%s from %s, but team is not an integer" % (toonId, str(team)))
|
||||
elif not toonId in self.toonIds[team]:
|
||||
self.notify.warning("Tried to remove toonId=%s from %s when it was not on that team. Ignoring request." % (toonId, self.getTeamName(team)))
|
||||
else:
|
||||
self.ignore(self.air.getAvatarExitEvent(toonId))
|
||||
self.toonIds[team].remove(toonId)
|
||||
del self.toonIdsToTeams[toonId]
|
||||
del self.toonId2joinTime[toonId]
|
||||
|
||||
self.notify.debug("Removed toonId=%s from %s." % (toonId, self.getTeamName(team)))
|
||||
self.notify.debug("toonIds:\n\t\t%s\n\t\t%s." % (self.toonIds[0], self.toonIds[1]))
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _removeAllToons(self):
|
||||
"""Removes all toons from the activity"""
|
||||
|
||||
self.notify.debug("removeAllToons")
|
||||
|
||||
for team in (PartyGlobals.TeamActivityTeams.LeftTeam, PartyGlobals.TeamActivityTeams.RightTeam, ):
|
||||
toonIds = list(self.toonIds[team])[:]
|
||||
self.notify.debug("initial toonIds for team %s = %s" % (team,toonIds))
|
||||
|
||||
for toonId in toonIds:
|
||||
self.notify.debug("removing toon %s" % toonId)
|
||||
self._removeToonFromTeam(toonId, team)
|
||||
|
||||
self.notify.debug("self.toonIds[team] is now %s" % self.toonIds[team])
|
||||
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
|
||||
def balanceTeams(self):
|
||||
"""
|
||||
Makes the teams roughly have the same number of toons.
|
||||
"""
|
||||
# We assume that the left team is smaller, but later make the correction
|
||||
smallerTeam = PartyGlobals.TeamActivityTeams.LeftTeam
|
||||
largerTeam = PartyGlobals.TeamActivityTeams.RightTeam
|
||||
|
||||
# If number of players in one team vs the other is more than 1
|
||||
if abs(len(self.toonIds[smallerTeam]) - len(self.toonIds[largerTeam])) > 1:
|
||||
# We make the correction to the assumption about the smaller team
|
||||
if len(self.toonIds[smallerTeam]) > len(self.toonIds[largerTeam]):
|
||||
smallerTeam = largerTeam
|
||||
largerTeam = 1 - smallerTeam
|
||||
|
||||
shiftedToons = False
|
||||
# Keep putting toons in the other team until
|
||||
# the number of toons in the larger team is close to the other
|
||||
# could be the same or one larger depending on the difference
|
||||
while abs(len(self.toonIds[largerTeam]) - len(self.toonIds[smallerTeam])) > 1:
|
||||
toondId = self.toonIds[largerTeam].pop()
|
||||
self.toonIds[smallerTeam].append(toondId)
|
||||
self.toonIdsToTeams[toondId] = smallerTeam
|
||||
shiftedToons = True
|
||||
|
||||
if self.isTeamFull(smallerTeam):
|
||||
break
|
||||
|
||||
# Finally send the new list out if any toons shifted teams
|
||||
if shiftedToons:
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
|
||||
def calcAdvantage(self):
|
||||
"""
|
||||
Calculates the advantage factor based on the team size.
|
||||
For example, if a team has 3 players and the other has 4, then
|
||||
the advantage will be 4/3 (1.333) for the smaller team over and 1
|
||||
for the larger team.
|
||||
"""
|
||||
self.resetAdvantage() # For good measure
|
||||
|
||||
smallerTeam = PartyGlobals.TeamActivityTeams.LeftTeam
|
||||
largerTeam = PartyGlobals.TeamActivityTeams.RightTeam
|
||||
|
||||
if len(self.toonIds[smallerTeam]) == 0 or len(self.toonIds[largerTeam]) == 0:
|
||||
return
|
||||
|
||||
# Find the actual large team
|
||||
if len(self.toonIds[smallerTeam]) > len(self.toonIds[largerTeam]):
|
||||
smallerTeam = largerTeam
|
||||
largerTeam = 1 - smallerTeam
|
||||
|
||||
# Set the advantage
|
||||
self.advantage[smallerTeam] = len(self.toonIds[largerTeam]) / float(len(self.toonIds[smallerTeam]))
|
||||
self.notify.debug("Advantage set to %s" % self.advantage)
|
||||
|
||||
# Send the advantage to members of the smaller team
|
||||
advantage = self.advantage[smallerTeam]
|
||||
|
||||
for toonId in self.toonIds[smallerTeam]:
|
||||
self.d_sendSetAdvantageToAvatarId(toonId, advantage)
|
||||
|
||||
def resetAdvantage(self):
|
||||
"""Resets the advantage for both teams back to 1.0"""
|
||||
self.advantage = [1.0, 1.0]
|
||||
|
||||
#===============================================================================
|
||||
# Handlers
|
||||
#===============================================================================
|
||||
|
||||
def _handleUnexpectedToonExit(self, toonId):
|
||||
"""
|
||||
Clean up after a toon that dropped.
|
||||
"""
|
||||
self.notify.debug("_handleUnexpectedToonExit( toonId=%d )" % toonId)
|
||||
|
||||
# do special responses based on game state
|
||||
state = self.activityFSM.getCurrentOrNextState()
|
||||
self.notify.debug("currentState = %s" % state)
|
||||
|
||||
if state in ("WaitToStart", 'WaitForEnough'):
|
||||
# remove toonId from self.toonIds and update clients
|
||||
self._removeToonFromTeam(toonId, self.getTeam(toonId))
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
|
||||
if state != 'WaitForEnough':
|
||||
# revert to WaitForEnough if necessary
|
||||
if not self.hasEnoughPlayers():
|
||||
self.activityFSM.request("WaitForEnough")
|
||||
|
||||
elif state in ("WaitClientsReady", "Active"):
|
||||
# To keep things simple the game will kick everyone out and reset
|
||||
# the game state.
|
||||
self._removeAllToons()
|
||||
self.activityFSM.request("WaitForEnough")
|
||||
|
||||
elif state == "Conclusion":
|
||||
del self.toonIdsToJellybeanRewards[toonId]
|
||||
# remove toonId from self.toonIds and update clients
|
||||
self._removeToonFromTeam(toonId, self.getTeam(toonId))
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
|
||||
#===============================================================================
|
||||
# Distributed
|
||||
#===============================================================================
|
||||
|
||||
# broadcast required
|
||||
# this method is for the client-side dc method setPlayersPerTeam
|
||||
def getPlayersPerTeam(self):
|
||||
return self._minPlayersPerTeam, self._maxPlayersPerTeam
|
||||
|
||||
# broadcast required
|
||||
def getDuration(self):
|
||||
return self._duration
|
||||
|
||||
# broadcast required
|
||||
def getCanSwitchTeams(self):
|
||||
return self._canSwitchTeams
|
||||
|
||||
# clsend airecv
|
||||
def toonJoinRequest(self, team):
|
||||
"""
|
||||
A toon is requesting to play tug of war.
|
||||
|
||||
Parameters:
|
||||
team: The team the toon is trying to join as a
|
||||
PartyGlobals.TeamActivityTeams value
|
||||
"""
|
||||
toonId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("toonJoinRequest( toon = %s team=%s )" %(toonId, self.getTeamName(team)))
|
||||
|
||||
# first make sure the toon is not in any activity
|
||||
if self.party.isInActivity(toonId):
|
||||
# decline the toon
|
||||
self.notify.warning("rejecting toon %d trying to join team %s, but is in some other activity" % (toonId, self.getTeamName(team)))
|
||||
self.sendToonJoinResponse(toonId, False, team)
|
||||
|
||||
return
|
||||
|
||||
# check team size limits first
|
||||
if self.isTeamFull(team):
|
||||
# decline the toon
|
||||
self.sendToonJoinResponse(toonId, False, team, PartyGlobals.DenialReasons.Full)
|
||||
|
||||
return
|
||||
|
||||
state = self.activityFSM.getCurrentOrNextState()
|
||||
self.notify.debug("toonJoinRequest state = " + state)
|
||||
|
||||
if state == "WaitForEnough":
|
||||
# add the toon
|
||||
self.sendToonJoinResponse(toonId, True, team)
|
||||
|
||||
# if we have enough players per team, request next state
|
||||
if self.hasEnoughPlayers():
|
||||
self.activityFSM.request("WaitToStart")
|
||||
elif state == "WaitToStart":
|
||||
self.sendToonJoinResponse(toonId, True, team)
|
||||
else:
|
||||
# decline the toon
|
||||
self.sendToonJoinResponse(toonId, False, team)
|
||||
|
||||
def sendToonJoinResponse(self, toonId, joined, team, denialReason=PartyGlobals.DenialReasons.Default):
|
||||
"""
|
||||
Broadcast to all clients whether or not this toonId has joined the
|
||||
activity.
|
||||
|
||||
Parameters:
|
||||
toonId- doId of the avatar that is being accepted/rejected
|
||||
joined- True if the avatar joined this activity, False otherwise
|
||||
team- which team the toon joined, as a PartyGlobals.TeamActivityTeams
|
||||
value
|
||||
denialReason- if the toon is being denied, the reason why
|
||||
"""
|
||||
# we purposely do not call the base class version as we track toons in
|
||||
# this activity ourselves
|
||||
if joined:
|
||||
self._addToonToTeam(toonId, team)
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
else:
|
||||
self.sendUpdateToAvatarId(toonId, "joinRequestDenied", [denialReason])
|
||||
|
||||
|
||||
# clsend airecv
|
||||
def toonExitRequest(self, team):
|
||||
"""
|
||||
Clients call this over the wire when they want to exit this
|
||||
activity, but need AI server permission to do so.
|
||||
|
||||
Parameters:
|
||||
team- which team the toon is on, as a PartyGlobals.TeamActivityTeams
|
||||
value
|
||||
"""
|
||||
# allow toons to leave if the activity is waiting for enough players or
|
||||
# waiting to start
|
||||
self.notify.debug("toonExitRequest( team=%s )" % self.getTeamName(team))
|
||||
|
||||
toonId = self.air.getAvatarIdFromSender()
|
||||
state = self.activityFSM.getCurrentOrNextState()
|
||||
|
||||
if state == "WaitForEnough":
|
||||
# allow the toon to exit
|
||||
self.sendToonExitResponse(toonId, True, team)
|
||||
elif state == "WaitToStart":
|
||||
# allow the toon to exit
|
||||
self.sendToonExitResponse(toonId, True, team)
|
||||
|
||||
# revert to WaitForEnough if necessary
|
||||
if not self.hasEnoughPlayers():
|
||||
self.activityFSM.request("WaitForEnough")
|
||||
else:
|
||||
# decline the toon
|
||||
self.sendToonExitResponse(toonId, False, team)
|
||||
|
||||
def sendToonExitResponse(self, toonId, exited, team, denialReason=PartyGlobals.DenialReasons.Default):
|
||||
"""
|
||||
Broadcast to all clients whether or not this toonId has left the activity.
|
||||
Subclasses should call this in response to recieving a toonExitRequest
|
||||
call.
|
||||
Parameters:
|
||||
toonId- id of the avatar that made the request to exit
|
||||
exited- True if the avatar exited this activity, False otherwise
|
||||
"""
|
||||
self.notify.debug("sendToonExitResponse(toonId=%s, exited=%s, team=%s)"%(toonId, bool(exited), self.getTeamName(team)))
|
||||
|
||||
if exited:
|
||||
self._removeToonFromTeam(toonId, team)
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
else:
|
||||
self.sendUpdateToAvatarId(toonId, "exitRequestDenied", [denialReason])
|
||||
|
||||
# clsend airecv
|
||||
def toonSwitchTeamRequest(self):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
state = self.activityFSM.getCurrentOrNextState()
|
||||
|
||||
if not self._canSwitchTeams:
|
||||
self.writeServerEvent(
|
||||
'suspicious',
|
||||
senderId,
|
||||
"Trying to switch teams when the team activity does not allow it.")
|
||||
return
|
||||
|
||||
team = self.getTeam(senderId)
|
||||
if team is None: # Might have exited at this point
|
||||
return
|
||||
|
||||
# Only allow switching before the activity starts
|
||||
if state != "WaitForEnough" and state != "WaitToStart":
|
||||
self.d_sendSwitchTeamRequestDeniedToAvatarId(senderId, PartyGlobals.DenialReasons.Default)
|
||||
return
|
||||
|
||||
otherTeam = 1 - team
|
||||
|
||||
if not self.isTeamFull(otherTeam):
|
||||
self.toonIds[team].remove(senderId)
|
||||
self.toonIds[otherTeam].append(senderId)
|
||||
self.toonIdsToTeams[senderId] = otherTeam
|
||||
|
||||
self.d_broadcastSetToonsPlaying()
|
||||
|
||||
# Check whether we need to change states at this point
|
||||
# Depending if the conditions (like if auto balance is on) for this activity there
|
||||
# may be enough players to start, or not enough.
|
||||
if state == "WaitForEnough" and self.hasEnoughPlayers():
|
||||
self.activityFSM.request("WaitToStart")
|
||||
elif state == "WaitToStart" and not self.hasEnoughPlayers():
|
||||
self.activityFSM.request("WaitForEnough")
|
||||
else:
|
||||
self.d_sendSwitchTeamRequestDeniedToAvatarId(senderId, PartyGlobals.DenialReasons.Full)
|
||||
|
||||
|
||||
# ai to avatar
|
||||
def d_sendSwitchTeamRequestDeniedToAvatarId(self, avId, reason):
|
||||
self.sendUpdateToAvatarId(
|
||||
avId,
|
||||
"switchTeamRequestDenied",
|
||||
[reason]
|
||||
)
|
||||
|
||||
# airecv clsend
|
||||
def toonReady(self):
|
||||
"""
|
||||
Clients call this over the wire when they are done reading the rules
|
||||
and are ready to play the activity.
|
||||
"""
|
||||
self.notify.debug("toonReady")
|
||||
|
||||
toonId = self.air.getAvatarIdFromSender()
|
||||
state = self.activityFSM.getCurrentOrNextState()
|
||||
|
||||
if state != "WaitClientsReady":
|
||||
self.notify.warning("Got an unexpected toonReady from toonId=%s while in state %s." % (toonId, state))
|
||||
elif not self.isToonPlaying(toonId):
|
||||
self.notify.warning("Got an unexpected toonReady from toonId=%s that is not currently in this activity." % toonId)
|
||||
else:
|
||||
# update set of ready toons
|
||||
self.readyToonIds.add(toonId)
|
||||
|
||||
# if we've gotten enough readies, then move on to next state
|
||||
if len(self.readyToonIds) == self.getNumToonsPlaying():
|
||||
self.activityFSM.request("Active")
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# broadcast ram
|
||||
def d_broadcastSetState(self, stateName, timestamp=0, data=0):
|
||||
self.sendUpdate("setState", [stateName, timestamp, data])
|
||||
|
||||
|
||||
# required broadcast ram
|
||||
def d_broadcastSetToonsPlaying(self):
|
||||
self.sendUpdate("setToonsPlaying", [self.toonIds[0], self.toonIds[1]])
|
||||
|
||||
|
||||
def d_sendSetAdvantageToAvatarId(self, toonId, advantage):
|
||||
self.sendUpdateToAvatarId(toonId, "setAdvantage", [advantage])
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# FSM
|
||||
#===============================================================================
|
||||
|
||||
def isState(self, state):
|
||||
"""
|
||||
Checks what the current state of the activity is right now.
|
||||
|
||||
Parameters
|
||||
state is what state we're checking for
|
||||
|
||||
Returns
|
||||
true if the current or next state matches the state we're checking for
|
||||
"""
|
||||
return self.activityFSM.getCurrentOrNextState() == state
|
||||
|
||||
def __delayedStateRequest(self, nextState):
|
||||
self.activityFSM.request(nextState)
|
||||
|
||||
def requestLater(self, stateName, delay, delayName):
|
||||
"""
|
||||
Schedule a do method later that requests a state change.
|
||||
"""
|
||||
self.cleanupRequestLater()
|
||||
|
||||
self._srTask = taskMgr.doMethodLater(
|
||||
delay,
|
||||
self.__delayedStateRequest,
|
||||
self.taskName(delayName),
|
||||
extraArgs=[stateName],
|
||||
)
|
||||
|
||||
def cleanupRequestLater(self):
|
||||
"""
|
||||
Cancels or cleans up the do method later that would request a state change.
|
||||
"""
|
||||
if self._srTask is not None:
|
||||
taskMgr.remove(self._srTask)
|
||||
self._srTask = None
|
||||
|
||||
def startWaitForEnough(self):
|
||||
"""
|
||||
Wait for for the minimum number players to join the activity.
|
||||
"""
|
||||
self.notify.debug("startWaitForEnough")
|
||||
|
||||
# put clients into this state
|
||||
self.d_broadcastSetState("WaitForEnough")
|
||||
|
||||
def finishWaitForEnough(self):
|
||||
"""
|
||||
There are enough players in the activity to start.
|
||||
Transitioning to WaitToStart.
|
||||
"""
|
||||
self.notify.debug("finishWaitForEnough")
|
||||
|
||||
|
||||
def startWaitToStart(self):
|
||||
"""
|
||||
Enough players are in.
|
||||
|
||||
Wait a few seconds before the activity begins to let more players
|
||||
come in and participate.
|
||||
"""
|
||||
self.notify.debug("startWaitToStart")
|
||||
|
||||
if self._shouldCalcAdvantage:
|
||||
self.resetAdvantage()
|
||||
|
||||
# put clients into this state
|
||||
self.d_broadcastSetState(
|
||||
"WaitToStart",
|
||||
timestamp=ClockDelta.globalClockDelta.getRealNetworkTime()
|
||||
)
|
||||
|
||||
# start the game after a fixed amount of time
|
||||
self.requestLater("WaitClientsReady", self._startDelay, "waitToStartTimer")
|
||||
|
||||
def finishWaitToStart(self):
|
||||
"""
|
||||
The wait time is over and now we're displaying the rules on the clients and
|
||||
waiting until everyone reads the rules.
|
||||
|
||||
Or we're below the number of toons required for the activity,
|
||||
so we go back to the WaitForEnough state.
|
||||
"""
|
||||
self.notify.debug("finishWaitToStart")
|
||||
|
||||
self.cleanupRequestLater()
|
||||
|
||||
|
||||
def startWaitClientsReady(self):
|
||||
"""
|
||||
In the clients with toons participating in this activity, we are
|
||||
displaying the rules, and we are waiting for all toons to read them
|
||||
before starting the activity.
|
||||
"""
|
||||
self.notify.debug("startWaitClientsReady")
|
||||
|
||||
self.readyToonIds.clear() # reset the list of ready toons
|
||||
|
||||
if self._shouldBalanceTeams:
|
||||
self.balanceTeams()
|
||||
|
||||
# put clients into new state
|
||||
self.d_broadcastSetState("Rules")
|
||||
|
||||
def finishWaitClientsReady(self):
|
||||
"""
|
||||
All toons have read the rules, and we transition to the Active state.
|
||||
Or perhaps toons have dropped out of the activity and we're back to WaitForEnough.
|
||||
"""
|
||||
self.notify.debug("finishWaitClientsReady")
|
||||
|
||||
|
||||
def startActive(self):
|
||||
"""
|
||||
The activity begins, and lasts a certain amount of minutes before jumping
|
||||
to the Conclusion.
|
||||
"""
|
||||
self.notify.debug("startActive")
|
||||
|
||||
if self._shouldCalcAdvantage:
|
||||
self.calcAdvantage()
|
||||
|
||||
# reset game variables
|
||||
self.toonIdsToJellybeanRewards.clear()
|
||||
|
||||
# put clients into this state
|
||||
self.d_broadcastSetState(
|
||||
"Active",
|
||||
timestamp=ClockDelta.globalClockDelta.getRealNetworkTime()
|
||||
)
|
||||
|
||||
# end the game after a fixed amount of time
|
||||
self.requestLater("Conclusion", self._duration, "gameTimer")
|
||||
|
||||
|
||||
def finishActive(self):
|
||||
"""
|
||||
The activity successfully ended and we are going to the conclusion,
|
||||
or a participating toon dropped out of the activity and we are going
|
||||
back to WaitForEnough.
|
||||
"""
|
||||
self.notify.debug("finishActive")
|
||||
|
||||
self.cleanupRequestLater()
|
||||
|
||||
|
||||
def startConclusion(self):
|
||||
"""
|
||||
Calculates the results and sends them to the participating clients for display.
|
||||
Wait a number of seconds before transitioning out.
|
||||
"""
|
||||
self.notify.debug("startConclusion")
|
||||
self.computeMatchResults()
|
||||
|
||||
# put clients into this state
|
||||
self.d_broadcastSetState(
|
||||
"Conclusion", # new state
|
||||
data=self.getConclusionData()
|
||||
)
|
||||
|
||||
# schedule to jump to next state
|
||||
self.requestLater("WaitForEnough", self._conclusionDuration, "rewardDuration")
|
||||
|
||||
def finishConclusion(self):
|
||||
"""
|
||||
Conclusion has ended and we're going back to WaitForEnough.
|
||||
"""
|
||||
self.notify.debug("finishConclusion")
|
||||
|
||||
self.cleanupRequestLater()
|
||||
|
||||
for toonId, reward in list(self.toonIdsToJellybeanRewards.items()):
|
||||
if reward > 0:
|
||||
self.sendUpdateToAvatarId(
|
||||
toonId,
|
||||
"showJellybeanReward", [
|
||||
reward,
|
||||
self.air.doId2do[toonId].getMoney(),
|
||||
self.getJellybeanRewardMessage(toonId, reward)
|
||||
])
|
||||
|
||||
# since we send the toon's current money in showJellybeanReward, that needs to happen before issueJellybeanRewards
|
||||
self.issueJellybeanRewards()
|
||||
self._removeAllToons()
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# Utility
|
||||
#===============================================================================
|
||||
|
||||
def getMaxPlayersPerTeam(self):
|
||||
"""Returns the maximum number for players allowed per team"""
|
||||
return self._maxPlayersPerTeam
|
||||
|
||||
def getMinPlayersPerTeam(self):
|
||||
"""Returns the minimum number for players allowed per team"""
|
||||
return self._minPlayersPerTeam
|
||||
|
||||
def getTeamName(self, team):
|
||||
"""
|
||||
Parameters
|
||||
team is the team index (see PartyGlobals.TeamActivityTeams)
|
||||
|
||||
Returns
|
||||
The team name
|
||||
"""
|
||||
return PartyGlobals.TeamActivityTeams(team)
|
||||
|
||||
|
||||
def isTeamFull(self, team):
|
||||
"""
|
||||
Checks if a team is full.
|
||||
|
||||
Parameters
|
||||
team is the team index (see PartyGlobals.TeamActivityTeams)
|
||||
|
||||
Returns
|
||||
true if the one of the teams is full.
|
||||
"""
|
||||
return (len(self.toonIds[team]) == self._maxPlayersPerTeam)
|
||||
|
||||
|
||||
def hasEnoughPlayers(self):
|
||||
"""
|
||||
Checks to see if there are enough players to start a game.
|
||||
If the config variable party-allow-single-player-teams is set,
|
||||
then the test passes if there's a least one player on every team.
|
||||
|
||||
Returns
|
||||
true if there are enough players for starting the game.
|
||||
"""
|
||||
if self._allowSinglePlayer:
|
||||
return (len(self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]) >= 1 or
|
||||
len(self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam]) >= 1)
|
||||
|
||||
elif self._shouldBalanceTeams:
|
||||
# This activity has enough players if the number of players on any side
|
||||
# is enough to create two teams.
|
||||
# The teams are balanced before right before the game starts.
|
||||
return (
|
||||
(len(self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]) +
|
||||
len(self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam])) >=
|
||||
(self._minPlayersPerTeam * 2)
|
||||
)
|
||||
|
||||
return (len(self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]) >= self._minPlayersPerTeam and
|
||||
len(self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam]) >= self._minPlayersPerTeam)
|
||||
|
||||
|
||||
def computeMatchResults(self):
|
||||
"""
|
||||
Used to determine the Jellybean rewards. Varies per activity implementation.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def getToonsPlaying(self):
|
||||
"""Returns a 2-dimensional list of Toon IDs in each team."""
|
||||
return [self.toonIds[0], self.toonIds[1]]
|
||||
|
||||
|
||||
def isInActivity(self, avId):
|
||||
"""Return true if the avId is busy with us."""
|
||||
result = False
|
||||
|
||||
for toonList in self.toonIds:
|
||||
if avId in toonList:
|
||||
result = True
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def isToonPlaying(self, toonId):
|
||||
"""Returns true if a toon is currently in the activity"""
|
||||
return (toonId in self.getToonIdsAsList())
|
||||
|
||||
|
||||
def getToonIdsAsList(self):
|
||||
"""Returns a list of doId's of all toons in this activity."""
|
||||
return (self.toonIds[0] + self.toonIds[1])
|
||||
|
||||
|
||||
def getNumToonsPlaying(self):
|
||||
return (len(self.toonIds[0]) + len(self.toonIds[1]))
|
||||
|
||||
|
||||
def getTeam(self, toonId):
|
||||
"""
|
||||
Utility function that returns the team toonId is on, as a
|
||||
PartyGlobals.TeamActivityTeams value, or None if toonId isn't on either team.
|
||||
|
||||
Returns
|
||||
team index or None
|
||||
"""
|
||||
for i in range(len(PartyGlobals.TeamActivityTeams)):
|
||||
if self.toonIds[i].count(toonId) > 0:
|
||||
return i
|
||||
|
||||
return None
|
||||
|
||||
def getJellybeanRewardMessage(self, toonId, reward):
|
||||
"""
|
||||
Returns the message that should be displayed when the game ends.
|
||||
This includes how many jellybeans they made
|
||||
"""
|
||||
return TTLocalizer.PartyTeamActivityRewardMessage % reward
|
||||
|
|
|
|||
|
|
@ -1,5 +1,156 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Jason Pratt
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI for DistributedPartyTrampolineActivity.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyTrampolineActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyTrampolineActivityAI')
|
||||
from direct.task import Task
|
||||
from direct.distributed import ClockDelta
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
from toontown.parties import PartyGlobals
|
||||
from toontown.ai.ToonBarrier import ToonBarrier
|
||||
from toontown.parties.DistributedPartyActivityAI import DistributedPartyActivityAI
|
||||
from toontown.parties.activityFSMs import TrampolineActivityFSM
|
||||
from toontown.toonbase import TTLocalizer
|
||||
|
||||
class DistributedPartyTrampolineActivityAI(DistributedPartyActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyTrampolineActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h, actId=PartyGlobals.ActivityIds.PartyTrampoline):
|
||||
DistributedPartyActivityAI.__init__(self, air, partyDoId, x, y, h, actId, PartyGlobals.ActivityTypes.GuestInitiated)
|
||||
|
||||
self.activityFSM = TrampolineActivityFSM(self)
|
||||
# bestHeightInfo is a tuple of toon's name and their height
|
||||
self.bestHeightInfo = ("", 0)
|
||||
self.accept("NewBestHeightInfo", self.newBestHeightInfo)
|
||||
|
||||
def generate(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("generate")
|
||||
self.activityFSM.request("Idle")
|
||||
|
||||
def toonJoinRequest(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("toonJoinRequest")
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if (self.activityFSM.state == "Idle") and (len(self.toonIds) == 0) and not self.party.isInActivity(senderId):
|
||||
self.sendToonJoinResponse(senderId, True)
|
||||
self.activityFSM.request("Rules")
|
||||
else:
|
||||
self.sendToonJoinResponse(senderId, False)
|
||||
|
||||
def toonReady(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("toonReady")
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if (self.activityFSM.state == "Rules") and (senderId in self.toonIds):
|
||||
self.activityFSM.request("Active")
|
||||
else:
|
||||
self.air.writeServerEvent("suspicious", senderId, "trampoline state not Rules or senderId not in toonIdsPlaying, in toonReady")
|
||||
|
||||
def toonExitDemand(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("toonExitDemand")
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if (self.activityFSM.state == "Active") and (senderId in self.toonIds):
|
||||
self.activityFSM.request("Idle")
|
||||
|
||||
def _handleUnexpectedToonExit(self, toonId):
|
||||
"""
|
||||
An avatar bailed out because he lost his connection or quit
|
||||
unexpectedly.
|
||||
"""
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("_handleUnexpectedToonExit( toonId=%s )" % toonId)
|
||||
self.activityFSM.request("Idle")
|
||||
DistributedPartyActivityAI._handleUnexpectedToonExit(self, toonId)
|
||||
|
||||
def reportHeightInformation(self, height):
|
||||
if height > self.bestHeightInfo[1]:
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
sender = self.air.doId2do[senderId]
|
||||
messenger.send("NewBestHeightInfo", [sender.getName(), height])
|
||||
|
||||
def newBestHeightInfo(self, toonName, height):
|
||||
self.bestHeightInfo = (toonName, height)
|
||||
self.sendUpdate("setBestHeightInfo", [toonName, height])
|
||||
|
||||
def getBestHeightInfo(self):
|
||||
return self.bestHeightInfo
|
||||
|
||||
def awardBeans(self, numBeansCollected, topHeight):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
|
||||
if numBeansCollected > PartyGlobals.TrampolineNumJellyBeans:
|
||||
self.air.writeServerEvent("suspicious", senderId, "Player claims to have collected more jelly beans (%d) than possible." % numBeansCollected)
|
||||
else:
|
||||
numWon = numBeansCollected
|
||||
if numWon == PartyGlobals.TrampolineNumJellyBeans:
|
||||
numWon += PartyGlobals.TrampolineJellyBeanBonus
|
||||
if self.air.holidayManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_DAY):
|
||||
numWon *= PartyGlobals.JellyBeanDayMultiplier
|
||||
resultsMessage = TTLocalizer.PartyTrampolineBonusBeanResults % (numBeansCollected, PartyGlobals.TrampolineJellyBeanBonus)
|
||||
else:
|
||||
if self.air.holidayManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_DAY):
|
||||
numWon *= PartyGlobals.JellyBeanDayMultiplier
|
||||
resultsMessage = TTLocalizer.PartyTrampolineBeanResults % numBeansCollected
|
||||
resultsMessage += "\n\n" + TTLocalizer.PartyTrampolineTopHeightResults % topHeight
|
||||
|
||||
self.toonIdsToJellybeanRewards = {senderId : numWon}
|
||||
self.sendUpdateToAvatarId(senderId, "showJellybeanReward", [numWon, self.air.doId2do[senderId].getMoney(), resultsMessage])
|
||||
# since we send the toon's current money in showJellybeanReward, that needs to happen before issueJellybeanRewards
|
||||
self.issueJellybeanRewards()
|
||||
|
||||
def requestAnim(self, request):
|
||||
self.sendUpdate("requestAnimEcho", [request])
|
||||
|
||||
def removeBeans(self, beansToRemove):
|
||||
self.sendUpdate("removeBeansEcho", [beansToRemove])
|
||||
|
||||
def sessionOver(self, task):
|
||||
"""
|
||||
Time is up. Kick the toon off the trampoline.
|
||||
"""
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("sessionOver")
|
||||
self.sendUpdate("leaveTrampoline")
|
||||
return Task.done
|
||||
|
||||
# FSM transition methods
|
||||
def startIdle(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("startIdle")
|
||||
# remove active toon
|
||||
if len( self.toonIds ) > 0:
|
||||
self.removeAllToons()
|
||||
# put clients into this state
|
||||
self.sendUpdate( "setState", ["Idle", # new state
|
||||
0] ) # dummy timestamp
|
||||
|
||||
def finishIdle(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("finishIdle")
|
||||
|
||||
def startRules(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("startRules")
|
||||
# we do not explicitly tell clients to enter this state as the
|
||||
# toonJoinResponse will prompt that change on the clients
|
||||
|
||||
def finishRules(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("finishRules")
|
||||
|
||||
def startActive(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("startActive")
|
||||
# put clients into this state
|
||||
self.sendUpdate( "setState", ["Active", # new state
|
||||
ClockDelta.globalClockDelta.getRealNetworkTime()] ) # start time
|
||||
|
||||
# setup to transition to Disabled after time is up
|
||||
taskMgr.doMethodLater(
|
||||
PartyGlobals.TrampolineDuration,
|
||||
self.sessionOver,
|
||||
self.taskName("waitForSessionOver"),
|
||||
)
|
||||
|
||||
def finishActive(self):
|
||||
DistributedPartyTrampolineActivityAI.notify.debug("finishActive")
|
||||
# clean up doMethodLater
|
||||
taskMgr.removeTasksMatching(self.taskName("waitForSessionOver"))
|
||||
|
||||
def delete(self):
|
||||
del self.activityFSM
|
||||
self.ignore("NewBestHeightInfo")
|
||||
DistributedPartyActivityAI.delete(self)
|
||||
|
|
|
|||
|
|
@ -329,7 +329,8 @@ class DistributedPartyTugOfWarActivity(DistributedPartyTeamActivity):
|
|||
|
||||
def __enableCollisions(self):
|
||||
for i in range(len(PartyGlobals.TeamActivityTeams)):
|
||||
self.accept('enterTugOfWarJoinCollision%d' % i, getattr(self, '_join%s' % PartyGlobals.TeamActivityTeams.getString(i)))
|
||||
teamName = PartyGlobals.TeamActivityTeams(i).name
|
||||
self.accept(f'enterTugOfWarJoinCollision{i}', getattr(self, f'_join{teamName}'))
|
||||
|
||||
def __disableCollisions(self):
|
||||
for i in range(len(PartyGlobals.TeamActivityTeams)):
|
||||
|
|
@ -662,7 +663,7 @@ class DistributedPartyTugOfWarActivity(DistributedPartyTeamActivity):
|
|||
self.sendUpdate('reportFallIn', [losingTeam])
|
||||
|
||||
def throwTeamInWater(self, losingTeam):
|
||||
self.notify.debug('throwTeamInWater( %s )' % PartyGlobals.TeamActivityTeams.getString(losingTeam))
|
||||
self.notify.debug('throwTeamInWater( %s )' % PartyGlobals.TeamActivityTeams(losingTeam))
|
||||
splashSet = False
|
||||
for toonId in self.toonIds[losingTeam]:
|
||||
self.fallenToons.append(toonId)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,195 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Rob Gordon, Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI control of tug of war activity in a party.
|
||||
# Changes:
|
||||
# - Nov 2009 Migrated to DistributedPartyTeamActivityAI
|
||||
#-------------------------------------------------------------------------------
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
from .DistributedPartyTeamActivityAI import DistributedPartyTeamActivityAI
|
||||
from . import PartyGlobals
|
||||
|
||||
class DistributedPartyTugOfWarActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyTugOfWarActivityAI')
|
||||
class DistributedPartyTugOfWarActivityAI(DistributedPartyTeamActivityAI):
|
||||
notify = directNotify.newCategory("DistributedPartyTugOfWarActivityAI")
|
||||
|
||||
MaxAbsGlobalOffset = 32
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyTeamActivityAI.__init__(
|
||||
self,
|
||||
air, partyDoId, x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyTugOfWar,
|
||||
minPlayersPerTeam = PartyGlobals.TugOfWarMinimumPlayersPerTeam,
|
||||
maxPlayersPerTeam = PartyGlobals.TugOfWarMaximumPlayersPerTeam,
|
||||
duration = PartyGlobals.TugOfWarDuration,
|
||||
conclusionDuration = PartyGlobals.TugOfWarConclusionDuration,
|
||||
startDelay = PartyGlobals.TugOfWarStartDelay,
|
||||
calcAdvantage = True,
|
||||
)
|
||||
self.notify.debug("__init__")
|
||||
|
||||
# Add up all the players forces for each side as soon as we get a
|
||||
# keyRate update from all players.
|
||||
self.forceDictList = [
|
||||
{}, # left team's forces, indexed by toonId
|
||||
{}, # right team's forces, indexed by toonId
|
||||
]
|
||||
self.toonIdsToKeyRates = {}
|
||||
self.numToonsReported = 0
|
||||
|
||||
self.globalOffset = 0.0 # how far the toons have moved from their initial position
|
||||
|
||||
# Variables for determining the outcome of the game. If
|
||||
# endedWithFall=True that means someone actually fell in the water.
|
||||
self.endedWithFall = False
|
||||
self.losingTeam = PartyGlobals.TeamActivityNeitherTeam
|
||||
|
||||
# Override because this code was migrated into the base class
|
||||
# And it was written assuming that it was 1v1
|
||||
if self._allowSinglePlayer:
|
||||
self.notify.warning("This team activity doesn't work party-team-activity-single-player config." +
|
||||
" This activity will ignore the config instead.")
|
||||
self._allowSinglePlayer = False
|
||||
|
||||
|
||||
# airecv clsend
|
||||
def toonReady(self):
|
||||
"""
|
||||
Clients call this over the wire when they are done reading the rules
|
||||
and are ready to play the activity.
|
||||
"""
|
||||
if DistributedPartyTeamActivityAI.toonReady(self):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
self.forceDictList[self.getTeam(senderId)][senderId] = 0.0
|
||||
|
||||
|
||||
def startWaitClientsReady(self):
|
||||
self.globalOffset = 0.0
|
||||
self.forceDictList = [{}, {}]
|
||||
|
||||
DistributedPartyTeamActivityAI.startWaitClientsReady(self)
|
||||
|
||||
|
||||
def startActive(self):
|
||||
# reset game variables
|
||||
self.toonIdsToKeyRates.clear()
|
||||
self.numToonsReported = 0
|
||||
self.losingTeam = PartyGlobals.TeamActivityNeitherTeam
|
||||
self.endedWithFall = False
|
||||
|
||||
DistributedPartyTeamActivityAI.startActive(self)
|
||||
|
||||
|
||||
# clsend airecv
|
||||
def reportKeyRateForce(self, keyRate, force):
|
||||
toonId = self.air.getAvatarIdFromSender()
|
||||
self.toonIdsToKeyRates[toonId] = keyRate
|
||||
# sometimes the game has cleaned up and we get an old update from a client
|
||||
if toonId in self.toonIdsToTeams:
|
||||
self.forceDictList[self.toonIdsToTeams[toonId]][toonId] = force
|
||||
|
||||
# send the keyrate for this toonId to the clients so they can update
|
||||
# the toon's animation (pulling or not)
|
||||
self.sendUpdate("updateToonKeyRate", [toonId, keyRate])
|
||||
|
||||
# send the current position to the clients if we have gotten all the clients forces
|
||||
self.numToonsReported += 1
|
||||
if self.numToonsReported == self.getNumToonsPlaying():
|
||||
self.numToonsReported = 0
|
||||
self.calculateOffset()
|
||||
self.sendUpdate("updateToonPositions", [self.globalOffset])
|
||||
|
||||
|
||||
def calculateOffset(self):
|
||||
# This function totals the forces on each side of the water. Then the difference
|
||||
# deltaF, between these forces is computed. This is multiplied by a constant, kMovement,
|
||||
# to determine what the deltaX should be - i.e. how much the toons on each side should
|
||||
# move as a result of one side applying more force than the other.
|
||||
|
||||
forceTotals = [0.0, 0.0] # left team force, right team force
|
||||
# total up all the toon forces on each side
|
||||
for teamIndex in [0,1]:
|
||||
for x in list(self.forceDictList[teamIndex].values()):
|
||||
forceTotals[teamIndex] += x
|
||||
|
||||
deltaF = forceTotals[1] - forceTotals[0]
|
||||
deltaX = deltaF * PartyGlobals.TugOfWarMovementFactor
|
||||
|
||||
self.globalOffset += deltaX
|
||||
|
||||
# make sure we don't move too far
|
||||
if self.globalOffset > self.MaxAbsGlobalOffset:
|
||||
self.globalOffset = self.MaxAbsGlobalOffset
|
||||
elif self.globalOffset < -self.MaxAbsGlobalOffset:
|
||||
self.globalOffset = -self.MaxAbsGlobalOffset
|
||||
|
||||
|
||||
def reportFallIn(self, losingTeam):
|
||||
self.notify.debug("reportFallIn( losingTeam=%s )" % PartyGlobals.TeamActivityTeams(losingTeam))
|
||||
|
||||
if losingTeam not in PartyGlobals.TeamActivityTeams:
|
||||
self.notify.warning("Got an invalid losingTeam value %d" %losingTeam)
|
||||
return
|
||||
|
||||
# if the losing team has already been reported and this report doesn't match
|
||||
if (self.losingTeam != PartyGlobals.TeamActivityNeitherTeam) and (losingTeam != self.losingTeam):
|
||||
self.notify.warning("Report of %s as the losingTeam doesn't match previously reported value. Ignoring." %PartyGlobals.TeamActivityTeams(losingTeam))
|
||||
return
|
||||
|
||||
curState = self.activityFSM.getCurrentOrNextState()
|
||||
if curState != "Active":
|
||||
# probably late report
|
||||
# must be in active state to go to conclusion
|
||||
self.notify.warning("got reportFallIn but state is %s, ignoring" % curState)
|
||||
return
|
||||
|
||||
self.losingTeam = losingTeam
|
||||
|
||||
if not self.endedWithFall:
|
||||
self.endedWithFall = True
|
||||
self.activityFSM.request("Conclusion")
|
||||
|
||||
|
||||
def computeMatchResults(self):
|
||||
"""
|
||||
Determine jelly bean rewards. If neither team fell in, determine if it
|
||||
was a decisive victory or too close to call (a tie).
|
||||
"""
|
||||
|
||||
# This seems like the easiest way to get this bean multiplier in for all these different jelly bean cases
|
||||
beanMultiplier = 1
|
||||
if self.air.holidayManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_DAY):
|
||||
beanMultiplier = PartyGlobals.JellyBeanDayMultiplier
|
||||
|
||||
|
||||
if self.endedWithFall:
|
||||
winningTeam = 1 - self.losingTeam # take advantage of enum nature of PartyGlobals.TeamActivityTeams
|
||||
for toonId in self.toonIds[winningTeam]:
|
||||
self.toonIdsToJellybeanRewards[toonId] = PartyGlobals.TugOfWarFallInWinReward * beanMultiplier
|
||||
for toonId in self.toonIds[self.losingTeam]:
|
||||
self.toonIdsToJellybeanRewards[toonId] = PartyGlobals.TugOfWarFallInLossReward * beanMultiplier
|
||||
else:
|
||||
# if there is no obvious winner, check how far the teams moved
|
||||
if abs(self.globalOffset) < PartyGlobals.TugOfWarTieThreshold:
|
||||
# a tie
|
||||
self.losingTeam = PartyGlobals.TeamActivityNeitherTeam
|
||||
for toonId in self.getToonIdsAsList():
|
||||
self.toonIdsToJellybeanRewards[toonId] = PartyGlobals.TugOfWarTieReward * beanMultiplier
|
||||
else:
|
||||
if self.globalOffset > 0:
|
||||
# right team wins
|
||||
self.losingTeam = PartyGlobals.TeamActivityTeams.LeftTeam
|
||||
else:
|
||||
# left team wins
|
||||
self.losingTeam = PartyGlobals.TeamActivityTeams.RightTeam
|
||||
winningTeam = 1 - self.losingTeam # take advantage of enum nature of PartyGlobals.TeamActivityTeams
|
||||
for toonId in self.toonIds[winningTeam]:
|
||||
self.toonIdsToJellybeanRewards[toonId] = PartyGlobals.TugOfWarWinReward * beanMultiplier
|
||||
for toonId in self.toonIds[self.losingTeam]:
|
||||
self.toonIdsToJellybeanRewards[toonId] = PartyGlobals.TugOfWarLossReward * beanMultiplier
|
||||
|
||||
|
||||
def getConclusionData(self):
|
||||
return self.losingTeam
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
|
||||
class DistributedPartyValentineDance20ActivityAI(DistributedObjectAI):
|
||||
from toontown.parties.DistributedPartyDanceActivityBaseAI import DistributedPartyDanceActivityBaseAI
|
||||
from toontown.parties import PartyGlobals
|
||||
class DistributedPartyValentineDance20ActivityAI(DistributedPartyDanceActivityBaseAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyValentineDance20ActivityAI')
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyDanceActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyValentineDance20,
|
||||
PartyGlobals.DancePatternToAnims,
|
||||
)
|
||||
|
|
@ -1,5 +1,15 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
|
||||
class DistributedPartyValentineDanceActivityAI(DistributedObjectAI):
|
||||
from toontown.parties.DistributedPartyDanceActivityBaseAI import DistributedPartyDanceActivityBaseAI
|
||||
from toontown.parties import PartyGlobals
|
||||
class DistributedPartyValentineDanceActivityAI(DistributedPartyDanceActivityBaseAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyValentineDanceActivityAI')
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyDanceActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyValentineDance,
|
||||
PartyGlobals.DancePatternToAnims,
|
||||
)
|
||||
|
|
@ -1,5 +1,18 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from toontown.parties.DistributedPartyJukeboxActivityBaseAI import DistributedPartyJukeboxActivityBaseAI
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyValentineJukebox40ActivityAI(DistributedObjectAI):
|
||||
class DistributedPartyValentineJukebox40ActivityAI(DistributedPartyJukeboxActivityBaseAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyValentineJukebox40ActivityAI')
|
||||
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyJukeboxActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyValentineJukebox40,
|
||||
PartyGlobals.PhaseToMusicData40,
|
||||
)
|
||||
self.notify.debug("Intializing.")
|
||||
|
|
@ -1,5 +1,26 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
# -------------------------------------------------------------------------------
|
||||
# Contact: Edmundo Ruiz (Schell Games)
|
||||
# Created: Oct 2008
|
||||
#
|
||||
# Purpose: AI component that manages which toons are currently dancing, who entered
|
||||
# and exited the dance floor, and broadcasts dance moves to all clients.
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyValentineJukeboxActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyValentineJukeboxActivityAI')
|
||||
from toontown.parties.DistributedPartyJukeboxActivityBaseAI import DistributedPartyJukeboxActivityBaseAI
|
||||
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
from direct.directnotify.DirectNotifyGlobal import *
|
||||
|
||||
class DistributedPartyValentineJukeboxActivityAI(DistributedPartyJukeboxActivityBaseAI):
|
||||
notify = directNotify.newCategory("DistributedPartyValentineJukeboxActivityAI")
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
self.notify.debug("Intializing.")
|
||||
DistributedPartyJukeboxActivityBaseAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyValentineJukebox,
|
||||
PartyGlobals.PhaseToMusicData,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from toontown.parties import PartyGlobals
|
||||
from toontown.parties.DistributedPartyTrampolineActivityAI import DistributedPartyTrampolineActivityAI
|
||||
|
||||
class DistributedPartyValentineTrampolineActivityAI(DistributedObjectAI):
|
||||
class DistributedPartyValentineTrampolineActivityAI(DistributedPartyTrampolineActivityAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyValentineTrampolineActivityAI')
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyTrampolineActivityAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h,
|
||||
PartyGlobals.ActivityIds.PartyValentineTrampoline,
|
||||
)
|
||||
self.notify.debug("Intializing.")
|
||||
|
|
@ -1,5 +1,13 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Mark Wojtowicz
|
||||
# Created: June 2010
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class DistributedPartyVictoryTrampolineActivityAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyVictoryTrampolineActivityAI')
|
||||
from toontown.parties import PartyGlobals
|
||||
from toontown.parties.DistributedPartyTrampolineActivityAI import DistributedPartyTrampolineActivityAI
|
||||
|
||||
class DistributedPartyVictoryTrampolineActivityAI(DistributedPartyTrampolineActivityAI):
|
||||
""" Reskinned trampoline for victory party holiday. """
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyTrampolineActivityAI.__init__(self, air, partyDoId, x, y, h, actId=PartyGlobals.ActivityIds.PartyVictoryTrampoline)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from toontown.parties import PartyGlobals
|
||||
from toontown.parties.DistributedPartyCatchActivityAI import DistributedPartyCatchActivityAI
|
||||
|
||||
class DistributedPartyWinterCatchActivityAI(DistributedObjectAI):
|
||||
class DistributedPartyWinterCatchActivityAI(DistributedPartyCatchActivityAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyWinterCatchActivityAI')
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyCatchActivityAI.__init__(self,
|
||||
air,
|
||||
partyDoId,
|
||||
x, y, h
|
||||
)
|
||||
self.notify.debug("Intializing.")
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from toontown.parties.DistributedPartyCogActivityAI import DistributedPartyCogActivityAI
|
||||
|
||||
class DistributedPartyWinterCogActivityAI(DistributedObjectAI):
|
||||
class DistributedPartyWinterCogActivityAI(DistributedPartyCogActivityAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyWinterCogActivityAI')
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h):
|
||||
DistributedPartyCogActivityAI.__init__(self, air, partyDoId, x, y, h)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from .DistributedPartyTrampolineActivityAI import DistributedPartyTrampolineActivityAI
|
||||
from . import PartyGlobals
|
||||
|
||||
class DistributedPartyWinterTrampolineActivityAI(DistributedObjectAI):
|
||||
class DistributedPartyWinterTrampolineActivityAI(DistributedPartyTrampolineActivityAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyWinterTrampolineActivityAI')
|
||||
|
||||
def __init__(self, air, partyDoId, x, y, h, actId=PartyGlobals.ActivityIds.PartyWinterTrampoline):
|
||||
DistributedPartyTrampolineActivityAI.__init__(self, air, partyDoId, x, y, h, actId)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class InviteInfoBase:
|
|||
def __str__(self):
|
||||
string = 'inviteKey=%d ' % self.inviteKey
|
||||
string += 'partyId=%d ' % self.partyId
|
||||
string += 'status=%s' % InviteStatus.getString(self.status)
|
||||
string += 'status=%s' % InviteStatus(self.status)
|
||||
return string
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from toontown.hood import Place
|
|||
from toontown.hood import SkyUtil
|
||||
from toontown.parties import PartyPlanner
|
||||
from toontown.parties.DistributedParty import DistributedParty
|
||||
|
||||
class Party(Place.Place):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('Party')
|
||||
|
||||
|
|
@ -230,9 +229,9 @@ class Party(Place.Place):
|
|||
zoneId = requestStatus['zoneId']
|
||||
avId = requestStatus['avId']
|
||||
shardId = requestStatus['shardId']
|
||||
if hoodId == ToontownGlobals.PartyHood and zoneId == self.getZoneId() and shardId == None:
|
||||
if hoodId == PartyHood and zoneId == self.getZoneId() and shardId == None:
|
||||
self.fsm.request('teleportIn', [requestStatus])
|
||||
elif hoodId == ToontownGlobals.MyEstate:
|
||||
elif hoodId == MyEstate:
|
||||
self.doneStatus = requestStatus
|
||||
self.getEstateZoneAndGoHome(requestStatus)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class PartyEditorListElement(DirectButton):
|
|||
(0.0, 0.0, 1.0, 1.0),
|
||||
(0.0, 1.0, 1.0, 1.0),
|
||||
(0.5, 0.5, 0.5, 1.0))
|
||||
assetName = PartyGlobals.DecorationIds.getString(self.id)
|
||||
assetName = PartyGlobals.DecorationIds(self.id).name
|
||||
if assetName == 'Hydra':
|
||||
assetName = 'StageSummer'
|
||||
geom = self.partyEditor.decorationModels.find('**/partyDecoration_%s' % assetName)
|
||||
|
|
@ -49,11 +49,11 @@ class PartyEditorListElement(DirectButton):
|
|||
(0.0, 1.0, 0.0, 1.0),
|
||||
(1.0, 1.0, 0.0, 1.0),
|
||||
(0.5, 0.5, 0.5, 1.0))
|
||||
iconString = PartyGlobals.ActivityIds.getString(self.id)
|
||||
if self.id == PartyGlobals.ActivityIds.PartyJukebox40:
|
||||
iconString = PartyGlobals.ActivityIds.getString(PartyGlobals.ActivityIds.PartyJukebox)
|
||||
iconString = PartyGlobals.ActivityIds(self.id).name
|
||||
if self.id == PartyGlobals.ActivityIds.PartyJukebox40.name:
|
||||
iconString = PartyGlobals.ActivityIds.PartyJukebox.name
|
||||
elif self.id == PartyGlobals.ActivityIds.PartyDance20:
|
||||
iconString = PartyGlobals.ActivityIds.getString(PartyGlobals.ActivityIds.PartyDance)
|
||||
iconString = PartyGlobals.ActivityIds.PartyDance.name
|
||||
geom = getPartyActivityIcon(self.partyEditor.activityIconsModel, iconString)
|
||||
scale = 0.35
|
||||
geom3_color = (0.5, 0.5, 0.5, 1.0)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class PartyInfoBase:
|
|||
string += 'start=%s ' % self.startTime
|
||||
string += 'end=%s ' % self.endTime
|
||||
string += 'isPrivate=%s ' % self.isPrivate
|
||||
string += 'inviteTheme=%s ' % InviteTheme.getString(self.inviteTheme)
|
||||
string += 'inviteTheme=%s ' % InviteTheme(self.inviteTheme).name
|
||||
string += 'activityList=%s ' % self.activityList
|
||||
string += 'decors=%s ' % self.decors
|
||||
string += 'status=%s' % self.status
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import calendar
|
|||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from panda3d.core import Vec3, Vec4, Point3, TextNode, VBase4
|
||||
from panda3d.otp import *
|
||||
from otp.otpbase import OTPLocalizer
|
||||
from direct.gui.DirectGui import DirectFrame, DirectButton, DirectLabel, DirectScrolledList, DirectCheckButton
|
||||
from direct.gui import DirectGuiGlobals
|
||||
|
|
@ -659,7 +660,7 @@ class PartyPlanner(DirectFrame, FSM):
|
|||
return invitees
|
||||
|
||||
def processAddPartyResponse(self, hostId, errorCode):
|
||||
PartyPlanner.notify.debug('processAddPartyResponse : hostId=%d errorCode=%s' % (hostId, PartyGlobals.AddPartyErrorCode.getString(errorCode)))
|
||||
PartyPlanner.notify.debug('processAddPartyResponse : hostId=%d errorCode=%s' % (hostId, PartyGlobals.AddPartyErrorCode(errorCode).name))
|
||||
goingBackAllowed = False
|
||||
if errorCode == PartyGlobals.AddPartyErrorCode.AllOk:
|
||||
goingBackAllowed = False
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ class PublicPartyGui(DirectFrame):
|
|||
text = TTLocalizer.PartyActivityNameDict[activityId]['generic']
|
||||
if number > 1:
|
||||
text += ' X %d' % number
|
||||
item = DirectLabel(relief=None, text=text, text_align=TextNode.ACenter, text_scale=0.05, text_pos=(0.0, -0.15), geom_scale=0.3, geom_pos=Vec3(0.0, 0.0, 0.07), geom=PartyUtils.getPartyActivityIcon(self.activityIconsModel, PartyGlobals.ActivityIds.getString(activityId)))
|
||||
item = DirectLabel(relief=None, text=text, text_align=TextNode.ACenter, text_scale=0.05, text_pos=(0.0, -0.15), geom_scale=0.3, geom_pos=Vec3(0.0, 0.0, 0.07), geom=PartyUtils.getPartyActivityIcon(self.activityIconsModel, PartyGlobals.ActivityIds(activityId).name))
|
||||
self.activityList.addItem(item)
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,5 +1,76 @@
|
|||
# Referenced Anesidora to help make some functions
|
||||
#-------------------------------------------------------------------------------
|
||||
# Contact: Shawn Patton
|
||||
# Created: Sep 2008
|
||||
#
|
||||
# Purpose: AI side of the party hat which is where toon's go to access public
|
||||
# parties.
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from direct.task import Task
|
||||
from toontown.parties import PartyGlobals
|
||||
|
||||
class DistributedPartyGateAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyGateAI')
|
||||
|
||||
def __init__(self, air):
|
||||
DistributedObjectAI.__init__(self, air)
|
||||
|
||||
def getPartyList(self, avId):
|
||||
"""
|
||||
Retrieves the list of all public parties for the given avatar ID (avId).
|
||||
This method checks if the provided avId matches the sender's avatar ID.
|
||||
If they do not match, it logs a suspicious event and returns without
|
||||
sending the party list. If they match, it sends the list of all public
|
||||
parties to the avatar.
|
||||
Args:
|
||||
avId (int): The avatar ID requesting the party list.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if avId != senderId:
|
||||
self.air.writeServerEvent('suspicious', avId, 'DistributedPartyGateAI.getPartyList avId: %s does not match sender avId: %s' % (avId, senderId))
|
||||
return
|
||||
self.sendUpdateToAvatarId(avId, 'listAllPublicParties', [self.air.partyManager.getAllPublicParties()])
|
||||
|
||||
def partyChoiceRequest(self, avId, shardId, zoneId):
|
||||
"""
|
||||
This function
|
||||
1. Checks if the avId matches the sender
|
||||
2. Gets all public parties
|
||||
3. Checks if the party is full
|
||||
4. Sends the party information to the avatar
|
||||
"""
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
if avId != senderId:
|
||||
self.air.writeServerEvent('suspicious', avId, 'DistributedPartyGateAI.partyChoiceRequest avId: %s does not match sender avId: %s' % (avId, senderId))
|
||||
return
|
||||
allPublicParties = self.air.partyManager.getAllPublicParties()
|
||||
|
||||
if len(allPublicParties) == 0:
|
||||
# No public parties available
|
||||
self.sendUpdateToAvatarId(avId, 'partyRequestDenied', [PartyGlobals.PartyGateDenialReasons.Unavailable])
|
||||
return
|
||||
else:
|
||||
for party in allPublicParties:
|
||||
if party[0] == shardId and party[1] == zoneId:
|
||||
# Party found
|
||||
if party[2] < PartyGlobals.MaxToonsAtAParty:
|
||||
# Party is not full
|
||||
self.sendUpdateToAvatarId(avId, 'setParty', [party])
|
||||
return
|
||||
else:
|
||||
# Party is full
|
||||
self.sendUpdateToAvatarId(avId, 'partyRequestDenied', [PartyGlobals.PartyGateDenialReasons.Full])
|
||||
return
|
||||
break #
|
||||
|
||||
else:
|
||||
# Party not found
|
||||
self.sendUpdateToAvatarId(avId, 'partyRequestDenied', [PartyGlobals.PartyGateDenialReasons.Unavailable])
|
||||
return
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ class EventsPage(ShtikerPage.ShtikerPage):
|
|||
textForActivity = activityName
|
||||
else:
|
||||
textForActivity = '%s x %d' % (activityName, count)
|
||||
iconString = PartyGlobals.ActivityIds.getString(activityBase.activityId)
|
||||
iconString = PartyGlobals.ActivityIds(activityBase.activityId).name
|
||||
geom = getPartyActivityIcon(self.activityIconsModel, iconString)
|
||||
label = DirectLabel(relief=None, geom=geom, geom_scale=0.38, geom_pos=Vec3(0.0, 0.0, -0.17), text=textForActivity, text_scale=TTLocalizer.EPactivityItemLabel, text_align=TextNode.ACenter, text_pos=(-0.01, -0.43), text_wordwrap=7.0)
|
||||
return label
|
||||
|
|
@ -201,7 +201,7 @@ class EventsPage(ShtikerPage.ShtikerPage):
|
|||
textForDecoration = decorationName
|
||||
else:
|
||||
textForDecoration = decorationName + ' x ' + str(count)
|
||||
assetName = PartyGlobals.DecorationIds.getString(decorBase.decorId)
|
||||
assetName = PartyGlobals.DecorationIds(decorBase.decorId).name
|
||||
if assetName == 'Hydra':
|
||||
assetName = 'StageSummer'
|
||||
label = DirectLabel(relief=None, geom=self.decorationModels.find('**/partyDecoration_%s' % assetName), text=textForDecoration, text_scale=TTLocalizer.EPdecorationItemLabel, text_align=TextNode.ACenter, text_pos=(-0.01, -0.43), text_wordwrap=7.0)
|
||||
|
|
@ -277,7 +277,7 @@ class EventsPage(ShtikerPage.ShtikerPage):
|
|||
textOfActivity = TTLocalizer.PartyActivityNameDict[activityId]['generic']
|
||||
else:
|
||||
textOfActivity = TTLocalizer.PartyActivityNameDict[activityId]['generic'] + ' x ' + str(countDict[activityId])
|
||||
geom = getPartyActivityIcon(self.activityIconsModel, PartyGlobals.ActivityIds.getString(activityId))
|
||||
geom = getPartyActivityIcon(self.activityIconsModel, PartyGlobals.ActivityIds(activityId).name)
|
||||
item = DirectLabel(relief=None, text=textOfActivity, text_align=TextNode.ACenter, text_scale=0.05, text_pos=(0.0, -0.15), geom_scale=0.3, geom_pos=Vec3(0.0, 0.0, 0.07), geom=geom)
|
||||
self.invitationActivityList.addItem(item)
|
||||
|
||||
|
|
|
|||
|
|
@ -3973,6 +3973,9 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo
|
|||
partyInfo.status = PartyGlobals.PartyStatus.CanStart
|
||||
found = True
|
||||
break
|
||||
else:
|
||||
self.notify.info('setPartyCanStart partyId does not match partyId in hostedParties %s' % partyId)
|
||||
print(self.hostedParties)
|
||||
|
||||
if not found:
|
||||
self.notify.warning("setPartyCanStart can't find partyId %s" % partyId)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class DistributedPartyManager(DistributedObject):
|
|||
if errorCode == PartyGlobals.AddPartyErrorCode.AllOk:
|
||||
base.localAvatar.setChatAbsolute('New party entered into database successfully.', CFSpeech | CFTimeout)
|
||||
else:
|
||||
base.localAvatar.setChatAbsolute('New party creation failed : %s' % PartyGlobals.AddPartyErrorCode.getString(errorCode), CFSpeech | CFTimeout)
|
||||
base.localAvatar.setChatAbsolute('New party creation failed : %s' % PartyGlobals.AddPartyErrorCode(errorCode).name, CFSpeech | CFTimeout)
|
||||
|
||||
def requestPartyZone(self, avId, zoneId, callback):
|
||||
if zoneId < 0:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -45,3 +45,5 @@ class ToontownUDRepository(ToontownInternalRepository):
|
|||
if __astron__:
|
||||
# Create our Astron login manager...
|
||||
self.astronLoginManager = self.generateGlobalObject(OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
|
||||
# create our DistributedPartyManagerUD
|
||||
self.partyManager = self.generateGlobalObject(OTP_DO_ID_TOONTOWN_PARTY_MANAGER, 'DistributedPartyManager')
|
||||
|
|
|
|||
Loading…
Reference in New Issue