This commit is contained in:
Michael G 2024-12-15 01:06:35 -06:00 committed by GitHub
commit 5960f7d461
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 7361 additions and 112 deletions

View File

@ -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'
"""

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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')

View File

@ -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')

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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:

View File

@ -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')

View File

@ -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)

View File

@ -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")

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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.")

View File

@ -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,
)

View File

@ -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.")

View File

@ -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)

View File

@ -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.")

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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')