diff --git a/toontown/ai/AprilFoolsManagerAI.py b/toontown/ai/AprilFoolsManagerAI.py new file mode 100644 index 0000000..3b5c2da --- /dev/null +++ b/toontown/ai/AprilFoolsManagerAI.py @@ -0,0 +1,33 @@ +############################################## +# Class: LowGravManagerAI +# This class handles April Fools changes +############################################## + +from toontown.ai import HolidayBaseAI +from toontown.ai import CostumeManagerAI +from toontown.toonbase import ToontownGlobals +from direct.showbase import DirectObject +from toontown.toonbase import TTLocalizer +from direct.directnotify import DirectNotifyGlobal + +class AprilFoolsManagerAI(CostumeManagerAI.CostumeManagerAI): + notify = DirectNotifyGlobal.directNotify.newCategory('AprilFoolsManagerAI') + + def __init__(self, air, holidayId): + CostumeManagerAI.CostumeManagerAI.__init__(self, air, holidayId) + + # Overridden function + def start(self): + CostumeManagerAI.CostumeManagerAI.start(self) + + estateManager = simbase.air.doFind("EstateManagerAI.EstateManagerAI") + if estateManager != None: + estateManager.startAprilFools() + + # Overridden function + def stop(self): + CostumeManagerAI.CostumeManagerAI.stop(self) + + estateManager = simbase.air.doFind("EstateManagerAI.EstateManagerAI") + if estateManager != None: + estateManager.stopAprilFools() diff --git a/toontown/ai/BlackCatHolidayMgrAI.py b/toontown/ai/BlackCatHolidayMgrAI.py index 41018c5..fd89a8a 100644 --- a/toontown/ai/BlackCatHolidayMgrAI.py +++ b/toontown/ai/BlackCatHolidayMgrAI.py @@ -15,4 +15,4 @@ class BlackCatHolidayMgrAI(HolidayBaseAI.HolidayBaseAI): bboard.post(BlackCatHolidayMgrAI.PostName) def stop(self): - bboard.remove(BlackCatHolidayMgrAI.PostName) \ No newline at end of file + bboard.remove(BlackCatHolidayMgrAI.PostName) diff --git a/toontown/ai/CostumeManagerAI.py b/toontown/ai/CostumeManagerAI.py new file mode 100644 index 0000000..a9bcaf6 --- /dev/null +++ b/toontown/ai/CostumeManagerAI.py @@ -0,0 +1,249 @@ +############################################## +# Class: CostumeManagerAI +# This class handles the loading of new +# models that will replace models in one +# or more hoods based on the holiday +# requirements. +############################################## + +from toontown.ai import HolidayBaseAI +from toontown.toonbase import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +from toontown.classicchars import * +from direct.task import Task +from direct.fsm import State +from toontown.hood import * +from direct.showbase import DirectObject +from toontown.toonbase import TTLocalizer +from toontown.classicchars import * +from toontown.classicchars import DistributedVampireMickeyAI, DistributedSuperGoofyAI, DistributedWesternPlutoAI +from toontown.classicchars import DistributedWitchMinnieAI, DistributedMinnieAI, DistributedPlutoAI +from toontown.hood import MMHoodDataAI, BRHoodDataAI + +class CostumeManagerAI(HolidayBaseAI.HolidayBaseAI, DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory('CostumeManagerAI') + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + self.__classicChars = {} + self.hoods = [] + + self.runningState = 1 + + self.cCharsSwitched = 0 + + # For use with magic words + self.stopForever = False + + # Overridden function + ###################################################### + # General format: if(self.holidayId == HOLIDAY_ID) + # Get hood and call switchChars with new hood + # and classicChar class. + ###################################################### + def start(self): + if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES): + self.accept("TTHoodSpawned", self.__welcomeValleySpawned) + self.accept("TTHoodDestroyed", self.__welcomeValleyDestroyed) + self.accept("GSHoodSpawned", self.__welcomeValleySpawned) + self.accept("GSHoodDestroyed", self.__welcomeValleyDestroyed) + + if hasattr(simbase.air, "holidayManager") and simbase.air.holidayManager is not None: + if self.holidayId in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[self.holidayId] != None: + return + + for hood in simbase.air.hoods: + if isinstance(hood, TTHoodDataAI.TTHoodDataAI): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, MMHoodDataAI.MMHoodDataAI): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, GSHoodDataAI.GSHoodDataAI): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, BRHoodDataAI.BRHoodDataAI): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + + elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES): + self.accept("TTHoodSpawned", self.__welcomeValleySpawned) + self.accept("TTHoodDestroyed", self.__welcomeValleyDestroyed) + self.accept("GSHoodSpawned", self.__welcomeValleySpawned) + self.accept("GSHoodDestroyed", self.__welcomeValleyDestroyed) + + if hasattr(simbase.air, "holidayManager"): + if self.holidayId in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[self.holidayId] != None: + return + + for hood in simbase.air.hoods: + # The character is neither transitioning or has transitioned into a different costume + if hasattr(hood, "classicChar") and hood.classicChar.transitionToCostume == 0 and hood.classicChar.diffPath == None: + if isinstance(hood, TTHoodDataAI.TTHoodDataAI): + # import pdb; pdb.set_trace() + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, BRHoodDataAI.BRHoodDataAI ): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, MMHoodDataAI.MMHoodDataAI ): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, DGHoodDataAI.DGHoodDataAI ): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, DLHoodDataAI.DLHoodDataAI ): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + elif isinstance(hood, GSHoodDataAI.GSHoodDataAI ): + self.hoods.append(hood) + self.__classicChars[str(hood)] = 1 + hood.classicChar.transitionCostume() + + # Overridden function + def stop(self): + self.ignoreAll() + del self.__classicChars + pass + + def goingToStop(self, stopForever=False): + # import pdb; pdb.set_trace() + self.notify.debug("GoingToStop") + self.stopForever = stopForever + self.runningState = 0 + if(self.holidayId in [ToontownGlobals.HALLOWEEN_COSTUMES, ToontownGlobals.APRIL_FOOLS_COSTUMES]): + self.ignore("TTHoodSpawned") + self.ignore("GSHoodSpawned") + for hood in self.hoods: + hood.classicChar.transitionCostume() + self.__classicChars[str(hood)] = 0 + + def getRunningState(self): + return self.runningState + + ######################################################## + # Trigger the switching of the character + ######################################################## + def triggerSwitch(self, curWalkNode, curChar): + if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES): + for hood in self.hoods: + if hood.classicChar == curChar: + hood.classicChar.fadeAway() + if(curChar.getName() == TTLocalizer.VampireMickey): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMickeyAI.DistributedMickeyAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Mickey): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedVampireMickeyAI.DistributedVampireMickeyAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.WitchMinnie): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMinnieAI.DistributedMinnieAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Minnie): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedWitchMinnieAI.DistributedWitchMinnieAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Goofy): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedSuperGoofyAI.DistributedSuperGoofyAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.SuperGoofy): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedGoofySpeedwayAI.DistributedGoofySpeedwayAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Pluto): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedWesternPlutoAI.DistributedWesternPlutoAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.WesternPluto): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedPlutoAI.DistributedPlutoAI, curWalkNode, hood]) + elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES): + for hood in self.hoods: + if hood.classicChar == curChar: + hood.classicChar.fadeAway() + if(curChar.getName() == TTLocalizer.Daisy): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMickeyAI.DistributedMickeyAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Mickey): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedDaisyAI.DistributedDaisyAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Goofy): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedDonaldAI.DistributedDonaldAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Donald): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedGoofySpeedwayAI.DistributedGoofySpeedwayAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Pluto): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMinnieAI.DistributedMinnieAI, curWalkNode, hood]) + elif(curChar.getName() == TTLocalizer.Minnie): + taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedPlutoAI.DistributedPlutoAI, curWalkNode, hood]) + + + ######################################################## + # Switched the classic character with a new one + # represented by class 'newChar' in 'hood'. + ######################################################## + def __switchChars(self, newChar, walkNode, hood): + self.notify.debug("SwitchingChars %s to %s" %(hood.classicChar, newChar)) + self.notify.debugStateCall(self) + hood.classicChar.requestDelete() + if hasattr(hood, "air") and hood.air: + hood.classicChar = newChar(hood.air) + hood.classicChar.generateWithRequired(hood.zoneId) + hood.addDistObj(hood.classicChar) + hood.classicChar.walk.setCurNode(walkNode) + hood.classicChar.fsm.request('Walk') + else: + self.notify.warning("Hood empty during character switch") + holidayDone = 1 + for classicChar in self.__classicChars.values(): + if classicChar == 1: + holidayDone = 0 + if holidayDone: + self.cCharsSwitched += 1 + if self.cCharsSwitched == len(self.__classicChars): + simbase.air.holidayManager.delayedEnd(self.holidayId, self.stopForever) + + ######################################################## + # Function to handle the spawning of a new welcome + # valley server + ######################################################## + + def __welcomeValleySpawned(self, newHood): + if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES): + self.__addAVampire(newHood) + elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES): + self.__aprilFoolsSwap(newHood) + + def __welcomeValleyDestroyed(self, newHood): + if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES): + self.__removeAVampire(newHood) + elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES): + self.__aprilFoolsRevert(newHood) + + def __aprilFoolsSwap(self, newHood): + for hood in self.hoods: + if hood == newHood: + return + + self.hoods.append(newHood) + self.__classicChars[str(newHood)] = 1 + newHood.classicChar.transitionCostume() + + def __aprilFoolsRevert(self, deadHood): + if str(deadHood) in self.__classicChars: + del self.__classicChars[str(deadHood)] + for hood in self.hoods: + if hood == deadHood: + self.hoods.remove(hood) + return + + def __addAVampire(self, newHood): + for hood in self.hoods: + if hood == newHood: + return + + self.hoods.append(newHood) + self.__classicChars[str(newHood)] = 1 + newHood.classicChar.transitionCostume() + + def __removeAVampire(self, deadHood): + if str(deadHood) in self.__classicChars: + del self.__classicChars[str(deadHood)] + for hood in self.hoods: + if hood == deadHood: + self.hoods.remove(hood) + return diff --git a/toontown/ai/DistributedBlackCatMgrAI.py b/toontown/ai/DistributedBlackCatMgrAI.py index 1ab8c8f..ba28331 100644 --- a/toontown/ai/DistributedBlackCatMgrAI.py +++ b/toontown/ai/DistributedBlackCatMgrAI.py @@ -1,5 +1,35 @@ from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed import DistributedObjectAI -class DistributedBlackCatMgrAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBlackCatMgrAI') +class DistributedBlackCatMgrAI(DistributedObjectAI.DistributedObjectAI): + """This object sits in the tutorial zone with Flippy and listens for + the avatar to say 'Toontastic!' when prompted to say something. At that + point, if the avatar is a cat, it gives them the 'black cat' DNA.""" + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedBlackCatMgrAI') + + def __init__(self, air, avId): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.avId = avId + + def getAvId(self): + return self.avId + + def doBlackCatTransformation(self): + avId = self.avId + if self.air.getAvatarIdFromSender() != avId: + self.air.writeServerEvent( + 'suspicious', avId, + '%s: expected msg from %s, got msg from %s' % ( + self.__class__.__name__, avId, self.air.getAvatarIdFromSender())) + return + + av = self.air.doId2do.get(self.avId) + if not av: + DistributedBlackCatMgrAI.notify.warning( + 'tried to turn av %s into a black cat, but they left' % avId) + else: + self.air.writeServerEvent('blackCatMade', avId, 'turning av %s into a black cat' % avId) + DistributedBlackCatMgrAI.notify.warning( + 'turning av %s into a black cat' % avId) + av.makeBlackCat() diff --git a/toontown/ai/DistributedPhaseEventMgrAI.py b/toontown/ai/DistributedPhaseEventMgrAI.py index 907995f..81574b8 100644 --- a/toontown/ai/DistributedPhaseEventMgrAI.py +++ b/toontown/ai/DistributedPhaseEventMgrAI.py @@ -1,5 +1,147 @@ +import datetime from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed import DistributedObjectAI -class DistributedPhaseEventMgrAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPhaseEventMgrAI') +class DistributedPhaseEventMgrAI(DistributedObjectAI.DistributedObjectAI): + """Distributed Object to tell the client what phase we're in.""" + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedPhaseEventMgrAI') + + def __init__(self, air, startAndEndTimes, phaseDates): + """Construct ourself and calc required fields.""" + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.startAndEndTimes = startAndEndTimes + self.phaseDates = phaseDates + self.curPhase = -1 + self.calcCurPhase(); + self.isRunning = False + self.calcIsRunning() + # we seem to be starting 5 seconds before the start time + self.isRunning = True + + def getDates(self): + """ + Send over the startAndEndTimes and the phaseDates + """ + holidayDates = [] + holidayDates.append(self.startAndEndTimes[-1].start) + for phaseDate in self.phaseDates: + holidayDates.append(phaseDate) + holidayDates.append(self.startAndEndTimes[-1].end) + + holidayDatesList = [] + for holidayDate in holidayDates: + holidayDatesList.append((holidayDate.year, holidayDate.month, holidayDate.day, \ + holidayDate.hour, holidayDate.minute, holidayDate.second)) + + return holidayDatesList + + def announceGenerate(self): + self.notify.debugStateCall(self) + self.switchPhaseTaskName = self.uniqueName("switchPhase") + self.setupNextPhase() + + def calcCurPhase(self): + self.notify.debugStateCall(self) + myTime = datetime.datetime.today() + result = self.getNumPhases()-1 + for index, phaseDate in enumerate( self.phaseDates): + if myTime < phaseDate: + result = index + break + self.curPhase = result + + def calcIsRunning(self): + self.notify.debugStateCall(self) + myTime = datetime.datetime.today() + foundInBetween = False + for startAndEnd in self.startAndEndTimes: + if startAndEnd.isInBetween(myTime): + foundInBetween = True + break + self.isRunning = foundInBetween + # note we will get deleted when the holiday stops + + def setupNextPhase(self): + """Setup a task to force us to go to the next phase if needed.""" + self.notify.debugStateCall(self) + curTime = datetime.datetime.today() + endTime = self.getPhaseEndTime(self.curPhase) + if curTime < endTime: + duration = endTime - curTime + waitTime = (duration.days * 60 *60 * 24) + duration.seconds + \ + duration.microseconds * 0.000001 + self.notify.debug("startingNextPhase in %s" % waitTime) + self.startSwitchPhaseTask(waitTime) + else: + self.notify.warning("at phase %s, endTime is in the past %s, not starting task to switch" % (self.curPhase, endTime)) + pass + + def startSwitchPhaseTask(self, waitTime): + """Startup our doMethodLater to switch to the next phase.""" + self.notify.debugStateCall(self) + taskMgr.doMethodLater(waitTime, self.doSwitchPhase, self.switchPhaseTaskName) + + def stopSwitchPhaseTask(self): + """Stop our switch phase task.""" + self.notify.debugStateCall(self) + taskMgr.removeTask(self.switchPhaseTaskName) + + def doSwitchPhase(self, task): + """We've waited long enough actually switch the phase now.""" + self.notify.debugStateCall(self) + if self.curPhase < 0: + self.notify.warning("doSwitchPhase doing nothing as curPhase=%s" % self.curPhase) + elif self.curPhase == self.getNumPhases()-1: + self.notify.debug("at last phase doing nothing") + else: + self.b_setCurPhase(self.curPhase+1) + self.notify.debug("switching phase, newPhase=%d" % self.curPhase) + self.setupNextPhase() + return task.done + + + def getPhaseEndTime(self, phase): + """Return a date time on when this phase will end.""" + self.notify.debugStateCall(self) + result = datetime.datetime.today() + if (0<=phase) and (phase < (self.getNumPhases() - 1)): + result =self.phaseDates[phase] + elif phase == self.getNumPhases() - 1: + result = self.startAndEndTimes[-1].end + else: + self.notify.warning("getPhaseEndTime got invalid phase %s returning now" % phase) + + return result + + def getNumPhases(self): + """Return how many phases we have.""" + result = len(self.phaseDates) + 1 + return result + + def getCurPhase(self): + return self.curPhase + + def getIsRunning(self): + return self.isRunning + + def setCurPhase(self, newPhase): + self.notify.debugStateCall(self) + self.curPhase = newPhase + + def d_setCurPhase(self, newPhase): + self.sendUpdate("setCurPhase", [newPhase]) + + def b_setCurPhase(self, newPhase): + self.setCurPhase(newPhase) + self.d_setCurPhase(newPhase) + + def forcePhase(self, newPhase): + """Magic word is forcing us to a new phase.""" + self.notify.debugStateCall(self) + if newPhase >= self.getNumPhases(): + self.notify.warning("ignoring newPhase %s" % newPhase) + return + self.b_setCurPhase(newPhase) + diff --git a/toontown/ai/DistributedScavengerHuntTargetAI.py b/toontown/ai/DistributedScavengerHuntTargetAI.py index 78ecc39..72499c8 100644 --- a/toontown/ai/DistributedScavengerHuntTargetAI.py +++ b/toontown/ai/DistributedScavengerHuntTargetAI.py @@ -1,5 +1,21 @@ from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed import DistributedObjectAI -class DistributedScavengerHuntTargetAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedScavengerHuntTarget') +class DistributedScavengerHuntTargetAI(DistributedObjectAI.DistributedObjectAI): + """ + This class is instanced several times by ScavengerHuntManagerAI. Each one sits in + in its assigned zone and listens for an event on the client + """ + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedScavengerHuntTargetAI') + + def __init__(self, air, hunt, goal, shMgr): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.goal = goal + self.shMgr = shMgr + + def attemptScavengerHunt(self): + avId = self.air.getAvatarIdFromSender() + self.shMgr.avatarAttemptingGoal(avId, self.goal) + diff --git a/toontown/ai/DistributedWinterCarolingTargetAI.py b/toontown/ai/DistributedWinterCarolingTargetAI.py index 21e9a26..bf72916 100644 --- a/toontown/ai/DistributedWinterCarolingTargetAI.py +++ b/toontown/ai/DistributedWinterCarolingTargetAI.py @@ -1,5 +1,22 @@ from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed import DistributedObjectAI +from . import DistributedScavengerHuntTargetAI -class DistributedWinterCarolingTargetAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedWinterCarolingTargetAI') +class DistributedWinterCarolingTargetAI(DistributedScavengerHuntTargetAI.DistributedScavengerHuntTargetAI): + """ + This class is instanced several times by WinterCarolingManagerAI. Each one sits in + in its assigned zone and listens for the client to say an SC + phrase. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'DistributedScavengerHuntTargetAI') + + def __init__(self, air, hunt, goal, totMgr): + DistributedScavengerHuntTargetAI.DistributedScavengerHuntTargetAI.__init__(self, \ + air, hunt, goal, totMgr) + + def attemptScavengerHunt(self): + avId = self.air.getAvatarIdFromSender() + self.shMgr.avatarAttemptingGoal(avId, self.goal) + diff --git a/toontown/ai/HolidayBaseAI.py b/toontown/ai/HolidayBaseAI.py index 2578e36..3a5fc8c 100644 --- a/toontown/ai/HolidayBaseAI.py +++ b/toontown/ai/HolidayBaseAI.py @@ -4,6 +4,9 @@ from direct.task import Task from toontown.effects import DistributedFireworkShowAI class HolidayBaseAI: + """ + Base class for all holidays + """ def __init__(self, air, holidayId): self.air = air @@ -14,3 +17,6 @@ class HolidayBaseAI: def stop(self): pass + + + diff --git a/toontown/ai/HolidayInfo.py b/toontown/ai/HolidayInfo.py new file mode 100644 index 0000000..43fc238 --- /dev/null +++ b/toontown/ai/HolidayInfo.py @@ -0,0 +1,264 @@ +################################################################# +# File: HolidayInfo.py +# Purpose: Coming Soon... +################################################################# + +################################################################# +# Python Specific Modules +################################################################# +import random +import time + +################################################################# +# Global Methods +################################################################# + +############################################################# +# Method: cmpTime +# Purpose: This method is used when sorting a list based +# on two time tuples. For the HolidayInfo tuples, +# we would like the list of tuples to be ordered +# in a chronological sequence for easy of transition +# from one holiday date to the next. +# Input: None +# Output: returns the comparison value +############################################################# +def cmpDates(tuple1, tuple2): + numValues = len(tuple1) + + for i in range(numValues): + if tuple1[i] > tuple2[i]: + return 1 + elif tuple1[i] < tuple2[i]: + return -1 + + return 0 + +################################################################# +# Class: ModfiedIter +# Purpose: The python iterator only allows one to go forward +# and ends +# NOTE: Implementation for this will likely change so that it +# will handle removals from the sequence gracefully. This +# currently does not do so. +################################################################# +class ModifiedIter: + def __init__(self, seq): + self._seq = seq + self._idx = 0 + self._storedIndex = 0 + + ############################################################# + # Method: current + # Purpose: This method returns the current element that the + # iterator references. + # Input: None + # Output: returns the current element + ############################################################# + def current(self): + try: + return self._seq[self._idx] + except IndexError: + raise StopIteration + + ############################################################# + # Method: next + # Purpose: This method emulates the python next method in that + # it updates the reference to the next element in + # the sequence. Unlike the python next method, it + # wraps around the sequence. + # Input: None + # Output: returns the new current element + ############################################################# + def __next__(self): + try: + lastIdx = len(self._seq) - 1 + self._idx = ((lastIdx == self._idx) and [0] or [self._idx+1])[0] + return self._seq[self._idx] + except IndexError: + raise StopIteration + + ############################################################# + # Method: prev + # Purpose: This method is similar to the python next method + # except that it updates the reference to the previous + # element in the sequence. This method wraps around + # around the sequence. + # Input: None + # Output: returns the new current element + ############################################################# + def prev(self): + try: + lastIdx = len(self._seq) - 1 + self._idx = ((self._idx == 0) and [lastIdx] or [self._idx-1])[0] + return self._seq[self._idx] + except IndexError: + raise StopIteration + + ############################################################# + # Method: peekNext + # Purpose: This method provides a look at functionality to + # see the next element in the list. + # Input: None + # Output: returns the next element + ############################################################# + def peekNext(self): + try: + idx = self._idx + lastIdx = len(self._seq) - 1 + idx = ((lastIdx == idx) and [0] or [idx+1])[0] + return self._seq[idx] + except: + raise StopIteration + + ############################################################# + # Method: peekPrev + # Purpose: This method provides a look at functionality to + # see the previous element in the list. + # Input: None + # Output: returns the next element + ############################################################# + def peekPrev(self): + try: + idx = self._idx + lastIdx = len(self._seq) - 1 + idx = ((idx == 0) and [lastIdx] or [idx-1])[0] + return self._seq[idx] + except IndexError: + raise StopIteration + + ############################################################# + # Method: setTo + # Purpose: This method sets the iterator to a known element + # Input: an element + # Output: true if element was found, false otherwise + ############################################################# + def setTo(self, element): + try: + index = self._seq.index(element) + self._idx = index + return True + except ValueError: + return False + + ############################################################# + # Method: store + # Purpose: This method stores the iterator state + # Input: None + # Output: None + ############################################################# + def store(self): + self._storedIndex = self._idx + + ############################################################# + # Method: store + # Purpose: This method restores the iterator state + # Input: None + # Output: None + ############################################################# + def restore(self): + self._idx = self._storedIndex + +################################################################# +# Class: HolidayInfo_Base +# Purpose: A Base Class for all derived. +################################################################# +class HolidayInfo_Base: + ############################################################# + # Method: __init__ + # Purpose: Provides initial construction of the HolidayInfo + # instance. + # Input: holidayClass - class type of the holiday, for + # instance - Fireworks. + # Output: None + ############################################################# + def __init__(self, holidayClass, displayOnCalendar): + self.__holidayClass = holidayClass + self.tupleList = [] + self.currElemIter = ModifiedIter(self.tupleList) + self.displayOnCalendar = displayOnCalendar + + ############################################################# + # Method: getClass + # Purpose: This method returns the class type of the + # Holiday. + # Input: None + # Output: returns Holiday Class Type + ############################################################# + def getClass(self): + return self.__holidayClass + + ############################################################# + # Method: getStartTime + # Purpose: This method returns the current start time of + # the holiday. + # Input: date - the current date represented as a tuple + # Output: returns current start time + ############################################################# + def getStartTime(self, date): + startTuple = self.currElemIter.current()[0] + return self.getTime(date, startTuple) + + ############################################################# + # Method: getEndTime + # Purpose: This method returns the current end time of + # the holiday. + # Input: date - the current date represented as a tuple + # Output: returns current end time + ############################################################# + def getEndTime(self, date): + endTuple = self.currElemIter.current()[1] + return self.getTime(date, endTuple) + + ############################################################# + # Method: getDate + # Purpose: This method returns the current date in a known format + # Input: None + # Output: date represented as a tuple + ############################################################# + def getDate(self): + localtime = time.localtime() + date = (localtime[0], # Year + localtime[1], # Month + localtime[2], # Day + localtime[6]) # WDay + + return date + + ############################################################# + # Method: getTime + # Purpose: This method returns the time based on the supplied + # date and t. + # Input: date - the current date represented as a tuple + # t - the current time tuple + # Output: returns the time in secs based on date and t + ############################################################# + def getTime(self, date, t): + return time.mktime((date[0], + date[1], + date[2], + t[0], + t[1], + t[2], + 0, + 0, + -1)) + + ############################################################# + # Method: getNumHolidays + # Purpose: This method returns the number of dates on which + # the holiday will be played. + # Input: None + # Output: returns the number of dates + ############################################################# + def getNumHolidays(self): + return len(self.tupleList) + + def hasPhaseDates(self): + """Returns true if the holiday ramps us over time to several different phases.""" + return False + + def getPhaseDates(self): + """Used when the holiday ramps us over time to several different phases. + Returns None when the holiday does not use phases""" + return None diff --git a/toontown/ai/HolidayInfoDaily.py b/toontown/ai/HolidayInfoDaily.py new file mode 100644 index 0000000..98909dd --- /dev/null +++ b/toontown/ai/HolidayInfoDaily.py @@ -0,0 +1,106 @@ +################################################################# +# File: HolidayInfoDaily.py +# Purpose: Contains the class implementation for daily Holidays. +################################################################# + +################################################################# +# Python Specific Modules +################################################################# +from toontown.ai.HolidayInfo import * + +################################################################# +# Python Specific Modules +################################################################# +import random +import time + +################################################################# +# Class: HolidayInfo_Daily +# Purpose: This HolidayInfo Derived Class is used for holidays +# that occur on a daily basis. For instance, running +# a holiday every day at 9 am to 12 pm. +################################################################# +class HolidayInfo_Daily(HolidayInfo_Base): + ############################################################# + # Method: __init__ + # Purpose: Provides initial construction of the Daily Holiday + # Info object. It generates the list of times that + # the holiday should be run every day. + # Input: holidayClass - class type of the holiday, for + # instance - Fireworks. + # timeList - a list of tuples containing the start + # and end dates for this holiday. + # Output: None + ############################################################# + def __init__(self, holidayClass, dateList, displayOnCalendar): + HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar) + dateElemIter = ModifiedIter(dateList) + for i in range(len(dateList)//2): + start = dateElemIter.current() + end = next(dateElemIter) + + self.tupleList.append((start, end)) + next(dateElemIter) + + ############################################################# + # Method: getNextHolidayTime + # Purpose: This method finds the next appropriate time to + # start this holiday. It searches through the list + # of time tuples, and performs the necessary + # computations for finding the time. + # Input: currTime - current time + # Output: returns the next start time of the holiday + ############################################################# + def getNextHolidayTime(self, currTime): + localTime = time.localtime() + date = (localTime[0], # year + localTime[1], # month + localTime[2], # day + ) + + for i in range(len(self.tupleList)): + # Retrieve the Start/End Tuples for the next time + # the holiday should be scheduled. + startTuple, endTuple = self.currElemIter.peekNext() + + # Retrieve the current Start Time and + # the next Start Time. + cStartTime = self.currElemIter.current()[0] + nStartTime = self.currElemIter.peekNext()[0] + + # If the current Start Time is larger than the + # next, we have reached the end of the list so + # we must schedule the + if cStartTime > nStartTime: + sTime = self.getTime((date[0], date[1], date[2]+1,), startTuple) + eTime = self.getTime((date[0], date[1], date[2]+1,), endTuple) + else: + sTime = self.getTime(date, startTuple) + eTime = self.getTime(date, endTuple) + if startTuple > endTuple: + eTime = self.getTime((date[0], date[1], date[2]+1,), endTuple) + else: + eTime = self.getTime(date, endTuple) + + # Iterate to the next time before we check validity + # of the time. + next(self.currElemIter) + if (currTime < eTime): + return sTime + + # We are back to the original element, thus we should + # schedule it for the next day. + start = self.currElemIter.current()[0] + return self.getTime((date[0], date[1], date[2]+1,), start) + + ############################################################# + # Method: adjustDate + # Purpose: This method adjusts the current day by one. This + # is typically called when an end time is less than + # a start time. + # Input: date - the date that needs to be adjusted + # Output: None + ############################################################# + def adjustDate(self, date): + return (date[0], date[1], date[2]+1, date[3]) + diff --git a/toontown/ai/HolidayInfoMonthly.py b/toontown/ai/HolidayInfoMonthly.py new file mode 100644 index 0000000..dcd6fdc --- /dev/null +++ b/toontown/ai/HolidayInfoMonthly.py @@ -0,0 +1,186 @@ +################################################################# +# File: HolidayInfoMonthly.py +# Purpose: Coming Soon... +################################################################# + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai.HolidayInfo import * + +################################################################# +# Python Specific Modules +################################################################# +import calendar +import random +import time +import functools +################################################################# +# Class: HolidayInfo_Monthly +# Purpose: Stores all relevant information regarding an event, +# such as the type of event. +################################################################# +class HolidayInfo_Monthly(HolidayInfo_Base): + ############################################################# + # Method: __init__ + # Purpose: Provides initial construction of the Monthly Holiday + # Info object. It generates the list of times that + # the holiday should be run every week. + # Input: holidayClass - class type of the holiday, for + # instance - Fireworks. + # dateDict - a dictionary containing the days + # and their corresponding time tuples. + # { 31: [((9, 0, 0), (12, 0, 0))] } + # Holiday starts at 9am PST and ends at + # 12pm PST on the 31st of Every Month. + # Output: None + ############################################################ + def __init__(self, holidayClass, dateList, displayOnCalendar): + HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar) + + dateElemIter = ModifiedIter(dateList) + for i in range(len(dateList)//2): + start = dateElemIter.current() + end = next(dateElemIter) + + finalTuple = self.__setTuplesMonthly(start, end) + self.tupleList.append(finalTuple) + next(dateElemIter) + + self.tupleList.sort(key=functools.cmp_to_key(cmpDates)) + + ############################################################# + # Method: __clampTimeTuples(self, sTuple, eTuple) + # Purpose: This method clamps any dates that go above the + # number of days in the particular month. + # For example, suppose the holiday ends on the 31st + # of every month. What about February? This clamps + # the holiday to the last day of February. + # Input: date - the current date represented as a tuple + # Output: returns the adjusted tuple pairing + # + # NOTE: This method also adjusts the start tuple day if + # the end date needs to be clamped. The adjustment is + # by the number of days that the end date is clamped. + # The reason for this is that it is assumed that the + # holiday should spand x number of days. We merely shift + # the days down if a clamping occurs. + ############################################################# + def __clampTimeTuples(self, sTuple, eTuple): + year = time.localtime()[0] + month = time.localtime()[1] + wday, numDays = calendar.monthrange(year, month) + + if (eTuple[0] > numDays) and (eTuple[0] > sTuple[0]): + dayOffset = (numDays - eTuple[0]) + + # This snippet of code emulates the C++ Ternary operator '?' + # Clamp the startTuple day to 1 if the offset takes it below 0. + day = ((sTuple[0]+dayOffset > 0) and [sTuple[0]+dayOffset] or [1])[0] + + sTuple = (day, sTuple[1], sTuple[2], sTuple[3]) + eTuple = (eTuple[0]+dayOffset, eTuple[1], eTuple[2], eTuple[3]) + + return (sTuple, eTuple) + + # date comes in the form of date[year, month, day] + # only day is relevant. + + ############################################################# + # Method: getTime + # Purpose: This method returns the time. Overrides the base + # definiton of HolidayInfo. + # Input: date - the current date represented as a tuple + # Output: returns the time in secs based on date and t + ############################################################# + def getTime(self, date, t): + # t is of the form (day, hour, min, sec) + # date is of the form (year, month, day, weekday) + return time.mktime((date[0], # year + date[1], # month + t[0], # day + t[1], # hour + t[2], # second + t[3], # minute + 0, + 0, + -1)) + + ############################################################# + # Method: getNextHolidayTime + # Purpose: This method finds the next appropriate time to + # start this holiday. It searches through the list + # of time tuples, and performs the necessary + # computations for finding the time. + # Input: currTime - current time + # Output: returns the next start time of the holiday + ############################################################# + def getNextHolidayTime(self, currTime): + currYear = time.localtime()[0] + sCurrMonth = time.localtime()[1] + eCurrMonth = sCurrMonth + + for i in range(len(self.tupleList)): + sDay = self.currElemIter.current()[0][0] + nDay = self.currElemIter.peekNext()[0][0] + + startTuple, endTuple = self.currElemIter.peekNext() + + # If the end day is less than the start day, it is + # in the proceeding month. Adjust the end month accordingly. + # This assures that the proper time tuple will be chosen from + # the list. + if endTuple[0] < startTuple[0]: + eCurrMonth += 1 + + if sDay > nDay: + # Since the next day is less than the current day, + # then we have reached the end of the list and the next + # date should be in the proceeding month. + sTime = self.getTime((currYear, sCurrMonth+1,), startTuple) + eTime = self.getTime((currYear, eCurrMonth+1,), endTuple) + elif sDay == nDay: + # Since the next tuple is of the same day, we must check + # the time tuples to see if the next time is greater than + # the current. If it is, that means we have reached the end + # of the list and should go into the next month. + curr = self.currElemIter.current()[0] + time1 = (curr[1], curr[2], curr[3]) + time2 = (startTuple[1], startTuple[2], startTuple[3]) + if time1 > time2: + sTime = self.getTime((currYear, sCurrMonth+1,), startTuple) + eTime = self.getTime((currYear, eCurrMonth+1,), endTuple) + else: + sTime = self.getTime((currYear, sCurrMonth,), startTuple) + eTime = self.getTime((currYear, eCurrMonth,), endTuple) + else: + # We have not reached the end of the list, calculate times + # accordingly. + sTime = self.getTime((currYear, sCurrMonth,), startTuple) + eTime = self.getTime((currYear, eCurrMonth,), endTuple) + + next(self.currElemIter) + if (currTime < eTime): + return sTime + + # We are back to the original element, thus we should schedule it + # for the next month. + start = self.currElemIter.current()[0] + return self.getTime((currYear, currMonth+1,), start) + + ############################################################# + # Method: adjustDate + # Purpose: This method adjusts the current day by a month. This + # is typically called when an end time is less than + # a start time. + # Input: date - the date that needs to be adjusted + # Output: None + ############################################################# + def adjustDate(self, date): + return (date[0], date[1]+1, date[2], date[3]) + + + + + + diff --git a/toontown/ai/HolidayInfoOncely.py b/toontown/ai/HolidayInfoOncely.py new file mode 100644 index 0000000..0d35b59 --- /dev/null +++ b/toontown/ai/HolidayInfoOncely.py @@ -0,0 +1,169 @@ +################################################################# +# File: HolidayInfoOncely.py +# Purpose: Coming Soon... +################################################################# + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai.HolidayInfo import * + +################################################################# +# Python Specific Modules +################################################################# +import random +import time +import datetime +import functools + +################################################################# +# Class: HolidayInfo_Oncely +# Purpose: Stores all relevant information regarding an event, +# such as the type of event. +################################################################# +class HolidayInfo_Oncely(HolidayInfo_Base): + ############################################################# + # Method: __init__ + # Purpose: Provides initial construction of the Oncely Holiday + # Info object. This type of holiday only happens once! + # + # Input: holidayClass - class type of the holiday, for + # instance - Fireworks. + # dateDict - a dictionary containing the Months, + # which each have a dictionary of days with + # their corresponding times. + # { Month.JULY: {31: [((9, 0, 0), (12, 0, 0))]} } + # Holiday starts at 9am PST and ends at + # 12pm PST on July 31st of Every Year. + # Output: None + ############################################################ + def __init__(self, holidayClass, dateList, displayOnCalendar, phaseDates = None, testHolidays = None): + """Phase dates adds a way for a ramping up holiday to go to the next phase.""" + # I briefly considered putting phase dates in the definition of the HolidayAI class + # but then that would put when it starts, and the different phase times in two + # separate files. This feels much safer. + # Implicit in this definition, if a holiday has 1 phase date, there are 2 phases + HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar) + dateElemIter = ModifiedIter(dateList) + for i in range(len(dateList)//2): + start = dateElemIter.current() + end = next(dateElemIter) + + self.tupleList.append((start, end)) + next(dateElemIter) + + self.tupleList.sort(key=functools.cmp_to_key(cmpDates)) + self.phaseDates = None + self.curPhase = 0 + if phaseDates: + self.processPhaseDates(phaseDates) + + self.testHolidays = testHolidays + + ############################################################# + # Method: getTime + # Purpose: This method returns the time. Overrides the base + # definiton of HolidayInfo. + # Input: date - the current date represented as a tuple + # Output: returns the time in secs based on date and t + ############################################################# + def getTime(self, date, t): + # t is of the form (year, month, day, hour, min, sec) + # date is of the form (year, month, day, weekday) - not used in this class + return time.mktime((t[0], # year + t[1], # month + t[2], # day + t[3], # hour + t[4], # second + t[5], # minute + 0, + 0, + -1)) + + ############################################################# + # Method: getNextHolidayTime + # Purpose: This type of holiday only happens once, so just return None + # + # Input: currTime - current time + # Output: returns the next start time of the holiday + ############################################################# + def getNextHolidayTime(self, currTime): + """ + Purpose: This method finds the next appropriate time to + start this holiday. It searches through the list + of time tuples, and performs the necessary + computations for finding the time. + Input: currTime - current time + Output: returns the next start time of the holiday, could be None + """ + result = None + + for i in range(len(self.tupleList)): + if i == 0: + # we need to setup currElem properly if we start + # in the middle of a oncely holiday with multiple starts + self.currElemIter.setTo(self.tupleList[0]) + startTuple = self.tupleList[i][0] + endTuple = self.tupleList[i][1] + + startNextTime = self.getTime(None, startTuple) + endNextTime = self.getTime(None, endTuple) + + if startNextTime <= currTime and \ + currTime <= endNextTime: + # we are between a start time and end time tuple + # start it now + result = currTime + break; + + if currTime < startNextTime and \ + currTime < endNextTime: + # we are waiting for the next pair of start,end times to arrive + result = startNextTime + break; + next(self.currElemIter) + return result + + ############################################################# + # Method: adjustDate + # Purpose: This method adjusts the current day by a year. This + # is typically called when an end time is less than + # a start time. + # Input: date - the date that needs to be adjusted + # Output: None + ############################################################# + def adjustDate(self, date): + return (date[0]+1, date[1], date[2], date[3]) + + def processPhaseDates(self, phaseDates): + """Convert the phase dates into datetimes.""" + self.phaseDates = [] + for curDate in phaseDates: + newTime = datetime.datetime(curDate[0], curDate[1], curDate[2], curDate[3], curDate[4], curDate[5]) + self.phaseDates.append(newTime) + + def getPhaseDates(self): + """Returns our phase dates, should be None if not used.""" + return self.phaseDates + + def hasPhaseDates(self): + """Returns False if we don't use phase dates.""" + if self.phaseDates: + return True + else: + return False + + ############################################################# + # Run holiday in test mode + # Used to invoke other holidays for debugging purposes + ############################################################# + + def isTestHoliday(self): + """ Returns true if running the holiday in test mode """ + if self.testHolidays: + return True + else: + return False + + def getTestHolidays(self): + return self.testHolidays \ No newline at end of file diff --git a/toontown/ai/HolidayInfoRelatively.py b/toontown/ai/HolidayInfoRelatively.py new file mode 100644 index 0000000..d0f62f1 --- /dev/null +++ b/toontown/ai/HolidayInfoRelatively.py @@ -0,0 +1,280 @@ +################################################################# +# File: HolidayInfoRelatively.py +# Purpose: To have a means of specifying a holiday as +# the position of a specific weekday in a month. +# For instance Halloween is the 5th Friday of October. +################################################################# + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai.HolidayInfo import * + +################################################################# +# Python Specific Modules +################################################################# +import calendar +import random +import time +from copy import deepcopy +import functools +from enum import IntEnum + + +Day = IntEnum("Day", ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', \ + 'FRIDAY', 'SATURDAY', 'SUNDAY'), start=0) + +################################################################# +# Class: HolidayInfo_Relatively +# Purpose: Stores all relevant information regarding an event, +# such as the type of event. +################################################################# +class HolidayInfo_Relatively(HolidayInfo_Base): + ############################################################# + # Method: __init__ + # Purpose: Provides initial construction of the Monthly Holiday + # Info object. It generates the list of times that + # the holiday should be run every week. + # Input: holidayClass - class type of the holiday, for + # instance - Halloween. + # dateList - The date is specified as a pair of the + # following: + # [(Month.OCTOBER, 5, Day.FRIDAY, 10, 0, 0), + # (Month.OCTOBER, 5, Day.FRIDAY, 15, 0, 0) ] + # This means that the holiday is on the 5th Friday of October + # between 10am and 3pm. + # Output: None + ############################################################ + def __init__(self, holidayClass, dateList, displayOnCalendar): + HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar) + + dateElemIter = ModifiedIter(dateList) + for i in range(len(dateList)//2): + start = dateElemIter.current() + end = next(dateElemIter) + + self.tupleList.append((start, end)) + next(dateElemIter) + + self.tupleList.sort(key=functools.cmp_to_key(cmpDates)) + self.weekDaysInMonth = [] # A matrix of the number of times a weekday repeats in a month + self.numDaysCorMatrix = [(28,0), (29, 1), + (30, 2), (31, 3)] # A matrix of the number of weekdays that repeat one extra + # time based on the number of days in the month. For instance + # in a month with 31 days, the first two week days occur + # one more time than the other days. + for i in range(7): # The minimum number of times a day repeats in a month + self.weekDaysInMonth.append((i,4)) + + ############################################################ + # Method: initRepMatrix + # Initialize the number of times weekdays get + # repeated in a month method. + ############################################################ + def initRepMatrix(self, year, month): + + for i in range(7): + self.weekDaysInMonth[i] = (i,4) + + startingWeekDay, numDays = calendar.monthrange(year, month) + + for i in range(4): + if(numDays == self.numDaysCorMatrix[i][0]): + break + + for j in range(self.numDaysCorMatrix[i][1]): # At this point we have a matrix of the weekdays and + self.weekDaysInMonth[startingWeekDay] = (self.weekDaysInMonth[startingWeekDay][0], + self.weekDaysInMonth[startingWeekDay][1]+1) # the number of times they repeat for the current month + startingWeekDay = (startingWeekDay+1)%7 + + ############################################################# + # Method: getTime + # Purpose: This method returns the time. Overrides the base + # definiton of HolidayInfo. + # Input: date - the current date represented as a tuple + # Output: returns the time in secs based on date and t + ############################################################# + def getTime(self, date, t): + # t is of the form (day, hour, min, sec) + # date is of the form (year, month, day, weekday) + repNum = t[1] + weekday = t[2] + self.initRepMatrix(date[0],t[0]) + while(self.weekDaysInMonth[weekday][1] < repNum): + repNum -= 1 + + day = self.dayForWeekday(date[0], t[0], weekday, repNum) + + return time.mktime((date[0], # year + t[0], # month + day, # day + t[3], # hour + t[4], # second + t[5], # minute + 0, + 0, + -1)) + + ############################################################# + # Method: getStartTime + # Purpose: This method returns the current start time of + # the holiday. + # Input: date - the current date represented as a tuple + # Output: returns current start time + ############################################################# + def getStartTime(self, date): + startTuple, endTuple = self.getUpdatedTuples(self.currElemIter.current()) + return self.getTime(date, startTuple) + + ############################################################# + # Method: getEndTime + # Purpose: This method returns the current end time of + # the holiday. + # Input: date - the current date represented as a tuple + # Output: returns current end time + ############################################################# + def getEndTime(self, date): + startTuple, endTuple = self.getUpdatedTuples(self.currElemIter.current()) + return self.getTime(date, endTuple) + + ############################################################# + # Method: getNextHolidayTime + # Purpose: This method finds the next appropriate time to + # start this holiday. It searches through the list + # of time tuples, and performs the necessary + # computations for finding the time. + # Input: currTime - current time + # Output: returns the next start time of the holiday + ############################################################# + def getNextHolidayTime(self, currTime): + sCurrYear = time.localtime()[0] + eCurrYear = sCurrYear + + for i in range(len(self.tupleList)): + startTuple, endTuple = self.getUpdatedTuples(self.currElemIter.peekNext()) + sMonth = startTuple[0] + nMonth = endTuple[0] + # If the end month is less than the start month, it is + # in the proceeding year. Adjust the end year accordingly. + # This assures that the proper time tuple will be chosen from + # the list. + if endTuple[0] < startTuple[0]: + eCurrYear += 1 + + if sMonth > nMonth: + # The holiday should be scheduled in the + # following year. There are two cases that + # may exist and they follow: + # Case 1: { May: [31], July: [(31)] } + # - Here, we end on July 31. The next + # time the holiday should start is on + # May 31, which would be in the following + # year. + # Case 2: { December: [(31, 1)], May: [31] } + # - Here, we end on January 1 of the new year, + # because this holiday spans from December + # to January. The next time the holiday should + # start is on May 31, which would be in the + # same year. + + # Check to see if we are already in the next + # year due to overlapping holiday. + cMonth = time.localtime()[1] + if cMonth > nMonth: + # We have not crossed over into the new week + # as found in case 1. + sTime = self.getTime((sCurrYear+1,), startTuple) + eTime = self.getTime((eCurrYear+1,), endTuple) + else: + # We have already started the new year as found + # in case 2. Adjust time normally. + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + elif sMonth == nMonth: + if sDay > nDay: + # Since the next day is less than the current day, + # then we have reached the end of the list and the next + # date should be in the proceeding year. + sTime = self.getTime((sCurrYear+1,), startTuple) + eTime = self.getTime((eCurrYear+1,), endTuple) + elif sDay == nDay: + # Since the next tuple is of the same day, we must check + # the time tuples to see if the next time is greater than + # the current. If it is, that means we have reached the end + # of the list and should go into the next year.. + curr = self.currElemIter.current()[0] + + time1 = (curr[3], curr[4], curr[5]) + time2 = (startTuple[3], startTuple[4], startTuple[5]) + + if time1 > time2: + sTime = self.getTime((sCurrYear+1,), startTuple) + eTime = self.getTime((eCurrYear+1,), endTuple) + else: + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + else: + # We have not reached the end of the list, calculate times + # accordingly. + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + else: + # We have not reached the end of the list, calculate times + # accordingly. + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + + next(self.currElemIter) + if (currTime < eTime): + return sTime + + # We are back to the original element, thus we should + # schedule it for the next year. + start = self.currElemIter.current()[0] + return self.getTime((sCurrYear+1,), start) + + ############################################################ + # Method: getUpdatedTuples + # Returns the corrected pair of tuples based on + # the current month and year information + ############################################################ + def getUpdatedTuples(self, tuples): + sTuple = list(deepcopy(tuples[0])) + eTuple = list(deepcopy(tuples[1])) + sRepNum = sTuple[1] + sWeekday = sTuple[2] + eWeekday = eTuple[2] + while(1): + eRepNum = eTuple[1] + self.initRepMatrix(time.localtime()[0], sTuple[0]) + while(self.weekDaysInMonth[sWeekday][1] < sRepNum): + sRepNum -= 1 + sDay = self.dayForWeekday(time.localtime()[0], sTuple[0], sWeekday, sRepNum) + + self.initRepMatrix(time.localtime()[0], eTuple[0]) + while(self.weekDaysInMonth[eWeekday][1] < eRepNum): + eRepNum -= 1 + nDay = self.dayForWeekday(time.localtime()[0], eTuple[0], eWeekday, eRepNum) + if(((nDay>sDay) and (eTuple[0] == sTuple[0]) \ + and ((eTuple[1] - sTuple[1]) <= (nDay-sDay+abs(eWeekday-sWeekday))/7)) \ + or (eTuple[0] != sTuple[0])): + break + + # Handles the case when the end weekday is less than the start + if(self.weekDaysInMonth[eWeekday][1] > eRepNum): + eRepNum += 1 + else: + eTuple[0] += 1 + eTuple[1] = 1 + return sTuple, eTuple + + ############################################################ + # Method: dayForWeekday(month, weekday, repNum) + # Returns the day for a given weekday that has repeated + # repNum times for that month + ############################################################ + def dayForWeekday(self, year, month, weekday, repNum): + monthDays = calendar.monthcalendar(year, month) + if(monthDays[0][weekday] == 0): + repNum += 1 + return monthDays[(repNum-1)][weekday] \ No newline at end of file diff --git a/toontown/ai/HolidayInfoWeekly.py b/toontown/ai/HolidayInfoWeekly.py new file mode 100644 index 0000000..00dff19 --- /dev/null +++ b/toontown/ai/HolidayInfoWeekly.py @@ -0,0 +1,205 @@ +################################################################# +# File: HolidayInfoWeekly.py +# Purpose: Coming Soon... +################################################################# + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai.HolidayInfo import * + +################################################################# +# Python Specific Modules +################################################################# +import random +import time +import functools + +################################################################# +# Class: HolidayInfo_Weekly +# Purpose: Stores all relevant information regarding a holiday. +# Note: Monday is designated as the first day of the week. +################################################################# +class HolidayInfo_Weekly(HolidayInfo_Base): + ############################################################# + # Method: __init__ + # Purpose: Provides initial construction of the Weekly Holiday + # Info object. It generates the list of times that + # the holiday should be run every week. + # Input: holidayClass - class type of the holiday, for + # instance - Fireworks. + # dateDict - a dictionary containing the weekdays + # and their corresponding time tuples. + # { Day.Monday: [((9, 0, 0), (12, 0, 0))] } + # Holiday starts at 9am PST and ends at + # 12pm PST every Monday. + # Output: None + ############################################################# + def __init__(self, holidayClass, dateList, displayOnCalendar): + + HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar) + + dateElemIter = ModifiedIter(dateList) + for i in range(len(dateList)//2): + start = dateElemIter.current() + end = next(dateElemIter) + + self.tupleList.append((start, end)) + next(dateElemIter) + + self.tupleList.sort(key=functools.cmp_to_key(cmpDates)) + + ############################################################# + # Method: getStartTime + # Purpose: This method returns the current start time of + # the holiday. Overrides the base definiton of + # HolidayInfo. + # Input: date - the current date represented as a tuple + # Output: returns current start time + ############################################################# + def getStartTime(self, date): + startTuple = self.currElemIter.current()[0] + return self.getTime(date, startTuple, True) + + ############################################################# + # Method: getEndTime + # Purpose: This method returns the current end time of + # the holiday. Overrides the base definiton of + # HolidayInfo. + # Input: date - the current date represented as a tuple + # Output: returns current end time + ############################################################# + def getEndTime(self, date): + endTuple = self.currElemIter.current()[1] + return self.getTime(date, endTuple, False) + + ############################################################# + # Method: getTime + # Purpose: This method returns the time. Overrides the base + # definiton of HolidayInfo. + # Input: date - the current date represented as a tuple + # t - the current time tuple + # isStart - True if we want starting time, + # False if we want end time. + # isNextWeek - True if time should be computed for + # the next week, false if it should be + # computed for this week. + # Output: returns the time in secs based on date and t + ############################################################# + def getTime(self, date, t, isStart = True, isNextWeek=False): + #print "Getting time for date = %s and t = %s" % (date, t) + cWDay = date[3] + sWDay = t[0] + dayOffset = sWDay - cWDay + if isNextWeek: + dayOffset += 7 + + day = date[2] + dayOffset + + actualTime = time.mktime((date[0], date[1], day, + t[1], t[2], t[3], + 0, 0, -1)) + + #print time.ctime(actualTime) + + return actualTime + + ############################################################# + # Method: getNextHolidayTime + # Purpose: This method finds the next appropriate time to + # start this holiday. It searches through the list + # of time tuples, and performs the necessary + # computations for finding the time. + # Input: currTime - current time + # Output: returns the next start time of the holiday + ############################################################# + def getNextHolidayTime(self, currTime): + date = self.getDate() + + foundTime = None + # look through the list of holidays for the first one we haven't hit + # (don't attempt to increment the week) + for start, end in self.tupleList: + nextStartTime = self.getTime(date, start, True, False) + nextEndTime = self.getTime(date, end, False, False) + # We add a one minute fudge factor to prevent the current + # holiday from restarting if its endHoliday doLater fires early. + # This has the side effect that if the AI starts within one minute + # of a holiday ending, it will NOT start the holiday. + if currTime + 59 < nextEndTime: + foundTime = nextStartTime + break + + if not foundTime: + # we have already passed the start time for all of these, + # add one week to the first one and use that + start, end = self.tupleList[0] + date = self.adjustDate(date) + foundTime = self.getTime(date, start, True, False) + + self.currElemIter.setTo((start, end)) + return foundTime + + """ + for i in xrange(len((self.tupleList))): + # Retrieve Starting WDay for the current Element + # and the next element in the sequence. + sWDay = self.currElemIter.current()[0][0] + nWDay = self.currElemIter.peekNext()[0][0] + + if sWDay > nWDay: + # The next date is in the following week. There + # are two cases that can exist and they follow: + # Case 1: [(1, 2), 3, 5] + # - Here, we have ended on a + # Saturday(5). The next time the holiday + # should fire up is on a Tuesday(1) of the + # next week. + # Case 2: [(6, 1), 3] + # - Here, we have ended on a Tuesday(1). The + # next time the holiday should fire will be + # on the Thursday(3) of the same week. + + # Check to see if we are already in the next + # week due to overlapping holiday. + cWDay = date[3] + + startTuple, endTuple = self.currElemIter.next() + if cWDay > nWDay: + # We have not started the new week as found + # in case 1. + sTime = self.getTime(date, startTuple, True, False) + else: + # We have already started the new week as found + # in case 2. Adjust time normally. + sTime = self.getTime(date, startTuple, True) + + else: + startTuple, endTuple = self.currElemIter.next() + sTime = self.getTime(date, startTuple, True) + + # Perform Check + if (currTime < sTime): + # Found next holiday day + return sTime + + # This means that we arrived back to the original + # starting place. Update date and find time for + # next starting of holiday. + date = (date[0], date[1], date[2]+7, date[3]) + startTuple = self.currElemIter.current()[0] + sTime = self.getTime(date, startTuple, True, True) + return sTime + """ + + ############################################################# + # Method: adjustDate + # Purpose: This method adjusts the current day by a week. This + # is typically called when an end time is less than + # a start time. + # Input: date - the date that needs to be adjusted + # Output: None + ############################################################# + def adjustDate(self, date): + return (date[0], date[1], date[2]+7, date[3]) + diff --git a/toontown/ai/HolidayInfoYearly.py b/toontown/ai/HolidayInfoYearly.py new file mode 100644 index 0000000..295f542 --- /dev/null +++ b/toontown/ai/HolidayInfoYearly.py @@ -0,0 +1,174 @@ +""" +# File: HolidayInfoYearly.py +# Purpose: Coming Soon... +""" + +# Toontown Specific Modules +from toontown.ai.HolidayInfo import * + +# Python Specific Modules +import random +import time +import functools + + +class HolidayInfo_Yearly(HolidayInfo_Base): + """ + Stores all relevant information regarding an event, + such as the type of event. + """ + + def __init__(self, holidayClass, dateList, displayOnCalendar): + """ + Purpose: Provides initial construction of the Monthly Holiday + Info object. It generates the list of times that + the holiday should be run every week. + Input: holidayClass - class type of the holiday, for + instance - Fireworks. + dateDict - a dictionary containing the Months, + which each have a dictionary of days with + their corresponding times. + { Month.JULY: {31: [((9, 0, 0), (12, 0, 0))]} } + Holiday starts at 9am PST and ends at + 12pm PST on July 31st of Every Year. + Output: None + """ + HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar) + dateElemIter = ModifiedIter(dateList) + for i in range(len(dateList)//2): + start = dateElemIter.current() + end = next(dateElemIter) + + self.tupleList.append((start, end)) + next(dateElemIter) + + self.tupleList.sort(key=functools.cmp_to_key(cmpDates)) + + def getTime(self, date, t): + """ + Purpose: This method returns the time. Overrides the base + definiton of HolidayInfo. + Input: date - the current date represented as a tuple + Output: returns the time in secs based on date and t + """ + # t is of the form (month, day, hour, min, sec) + # date is of the form (year, month, day, weekday) + return time.mktime((date[0], # year + t[0], # month + t[1], # day + t[2], # hour + t[3], # second + t[4], # minute + 0, + 0, + -1)) + + def getNextHolidayTime(self, currTime): + """ + Purpose: This method finds the next appropriate time to + start this holiday. It searches through the list + of time tuples, and performs the necessary + computations for finding the time. + Input: currTime - current time + Output: returns the next start time of the holiday + """ + sCurrYear = time.localtime()[0] + eCurrYear = sCurrYear + + for i in range(len(self.tupleList)): + sMonth = self.currElemIter.current()[0][0] + nMonth = self.currElemIter.peekNext()[0][0] + + startTuple, endTuple = self.currElemIter.peekNext() + # If the end month is less than the start month, it is + # in the proceeding year. Adjust the end year accordingly. + # This assures that the proper time tuple will be chosen from + # the list. + if endTuple[0] < startTuple[0]: + eCurrYear += 1 + + if sMonth > nMonth: + # The holiday should be scheduled in the + # following year. There are two cases that + # may exist and they follow: + # Case 1: { May: [31], July: [(31)] } + # - Here, we end on July 31. The next + # time the holiday should start is on + # May 31, which would be in the following + # year. + # Case 2: { December: [(31, 1)], May: [31] } + # - Here, we end on January 1 of the new year, + # because this holiday spans from December + # to January. The next time the holiday should + # start is on May 31, which would be in the + # same year. + + # Check to see if we are already in the next + # year due to overlapping holiday. + cMonth = time.localtime()[0] + if cMonth > nMonth: + # We have not crossed over into the new week + # as found in case 1. + sTime = self.getTime((sCurrYear+1,), startTuple) + eTime = self.getTime((eCurrYear+1,), endTuple) + else: + # We have already started the new year as found + # in case 2. Adjust time normally. + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + elif sMonth == nMonth: + sDay = self.currElemIter.current()[0][1] + nDay = self.currElemIter.peekNext()[0][1] + + if sDay > nDay: + # Since the next day is less than the current day, + # then we have reached the end of the list and the next + # date should be in the proceeding year. + sTime = self.getTime((sCurrYear+1,), startTuple) + eTime = self.getTime((eCurrYear+1,), endTuple) + elif sDay == nDay: + # Since the next tuple is of the same day, we must check + # the time tuples to see if the next time is greater than + # the current. If it is, that means we have reached the end + # of the list and should go into the next year.. + curr = self.currElemIter.current()[0] + + time1 = (curr[2], curr[3], curr[4]) + time2 = (startTuple[2], startTuple[3], startTuple[4]) + + if time1 > time2: + sTime = self.getTime((sCurrYear+1,), startTuple) + eTime = self.getTime((eCurrYear+1,), endTuple) + else: + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + else: + # We have not reached the end of the list, calculate times + # accordingly. + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + else: + # We have not reached the end of the list, calculate times + # accordingly. + sTime = self.getTime((sCurrYear,), startTuple) + eTime = self.getTime((eCurrYear,), endTuple) + + next(self.currElemIter) + if (currTime < eTime): + return sTime + + # We are back to the original element, thus we should + # schedule it for the next year. + start = self.currElemIter.current()[0] + return self.getTime((sCurrYear+1,), start) + + def adjustDate(self, date): + """ + Purpose: This method adjusts the current day by a year. This + is typically called when an end time is less than + a start time. + Input: date - the date that needs to be adjusted + Output: None + """ + return (date[0]+1, date[1], date[2], date[3]) + diff --git a/toontown/ai/HolidayManagerAI.py b/toontown/ai/HolidayManagerAI.py index 9aa3538..f47b928 100644 --- a/toontown/ai/HolidayManagerAI.py +++ b/toontown/ai/HolidayManagerAI.py @@ -1,16 +1,2109 @@ -from direct.directnotify import DirectNotifyGlobal -from toontown.toonbase import ToontownGlobals +################################################################# +# File: HolidayManagerAI.py +# Purpose: Coming Soon... +################################################################# +import datetime +from datetime import timedelta +from enum import IntEnum +################################################################# +# Direct Specific Modules +################################################################# +from direct.directnotify import DirectNotifyGlobal +from direct.showbase.PythonUtil import SingletonError +from direct.task import Task + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai.HolidayInfoOncely import * +from toontown.ai.HolidayInfoDaily import * +from toontown.ai.HolidayInfoWeekly import * +from toontown.ai.HolidayInfoMonthly import * +from toontown.ai.HolidayInfoYearly import * +from toontown.ai.HolidayInfoRelatively import * +from toontown.ai import HolidayRepeaterAI +from toontown.effects import FireworkManagerAI +from toontown.fishing import BingoNightHolidayAI +from toontown.suit import HolidaySuitInvasionManagerAI +from toontown.ai import BlackCatHolidayMgrAI +from toontown.ai import ScavengerHuntMgrAI +from toontown.ai import TrickOrTreatMgrAI +from toontown.ai import WinterCarolingMgrAI +from toontown.ai import ResistanceEventMgrAI +from toontown.ai import PolarPlaceEventMgrAI +from toontown.toonbase import ToontownGlobals +from toontown.racing import RaceManagerAI +from toontown.minigame import TrolleyHolidayMgrAI +from toontown.minigame import TrolleyWeekendMgrAI +from toontown.ai import RoamingTrialerWeekendMgrAI +from toontown.ai import CostumeManagerAI +from toontown.ai import AprilFoolsManagerAI +from toontown.ai import HydrantZeroHolidayAI +from toontown.ai import MailboxZeroHolidayAI +from toontown.ai import TrashcanZeroHolidayAI +from toontown.ai import HydrantBuffHolidayAI +from toontown.ai import MailboxBuffHolidayAI +from toontown.ai import TrashcanBuffHolidayAI +from toontown.ai import ValentinesDayMgrAI +from toontown.ai import SillyMeterHolidayAI +################################################################# +# Python Specific Modules +################################################################# +import random +import time + +################################################################# +# Global Enumerations and Constants +################################################################# +Month = IntEnum('Month', ('JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', \ + 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', \ + 'OCTOBER', 'NOVEMBER', 'DECEMBER')) + +Day = IntEnum('Day', 'MONDAY TUESDAY WEDNESDAY THURSDAY \ + FRIDAY SATURDAY SUNDAY') + +OncelyMultipleStartHolidays = (ToontownGlobals.COLD_CALLER_INVASION, + ToontownGlobals.BEAN_COUNTER_INVASION, + ToontownGlobals.DOUBLE_TALKER_INVASION, + ToontownGlobals.DOWNSIZER_INVASION, + ToontownGlobals.DOWN_SIZER_INVASION, + ToontownGlobals.MOVER_AND_SHAKER_INVASION, + ToontownGlobals.DOUBLETALKER_INVASION, + ToontownGlobals.YES_MAN_INVASION, + ToontownGlobals.PENNY_PINCHER_INVASION, + ToontownGlobals.TIGHTWAD_INVASION, + ToontownGlobals.TELEMARKETER_INVASION, + ToontownGlobals.HEADHUNTER_INVASION, + ToontownGlobals.SPINDOCTOR_INVASION, + ToontownGlobals.MONEYBAGS_INVASION, + ToontownGlobals.TWOFACES_INVASION, + ToontownGlobals.NAME_DROPPER_INVASION, + ToontownGlobals.MICROMANAGER_INVASION, + ToontownGlobals.NUMBER_CRUNCHER_INVASION, + ToontownGlobals.AMBULANCE_CHASER_INVASION, + ToontownGlobals.MINGLER_INVASION, + ToontownGlobals.LOANSHARK_INVASION, + ToontownGlobals.CORPORATE_RAIDER_INVASION, + ToontownGlobals.LEGAL_EAGLE_INVASION, + ToontownGlobals.MR_HOLLYWOOD_INVASION, + ToontownGlobals.ROBBER_BARON_INVASION, + ToontownGlobals.BIG_WIG_INVASION, + ToontownGlobals.BIG_CHEESE_INVASION, + ) + +# These variables are too useful in debugging holidays, keeping them around +# StartMinute = 19 +# StartHour = 19 + +# we are creating this system so it's easier to start holidays on the test server ahead of schedule +TestServerHolidayDaysAhead = simbase.config.GetInt("test-server-holiday-days-ahead", 0) +TestServerHolidayTimeDelta = timedelta(days = TestServerHolidayDaysAhead) + +# TODO figure out how to make this work for more than just oncely holidays +OriginalHolidays = { + ToontownGlobals.HYDRANT_ZERO_HOLIDAY: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.MAY.value, 5, 8, 0), # firstMoveArmUp1 + datetime.datetime( 2010, Month.JUNE, 12, 11, 55),], + 'phaseDates': [datetime.datetime( 2010, Month.MAY, 9, 11, 0o5), # firstMoveStruggle + datetime.datetime( 2010, Month.MAY, 13, 11, 0o5), # firstMoveArmUp2 + datetime.datetime( 2010, Month.MAY, 18, 11, 0o5), # firstMoveJump hydrants around hydrant zero animate + datetime.datetime( 2010, Month.MAY, 21, 16, 0o5), # firstMoveJumpBalance + datetime.datetime( 2010, Month.MAY, 22, 15, 30), # firstMoveArmUp3 Hydrant Zero and his hydrant pals get more elaborate animations + datetime.datetime( 2010, Month.JUNE, 3, 15, 30), # firstMoveJumpSpin + ], + }, + + ToontownGlobals.TRASHCAN_ZERO_HOLIDAY: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.MAY, 8, 12, 15), # firstMoveLidFLip1 + datetime.datetime( 2010, Month.JUNE, 12, 11, 55),], + 'phaseDates': [datetime.datetime( 2010, Month.MAY, 11, 11, 0o5), # firstMoveStruggle + datetime.datetime( 2010, Month.MAY, 15, 11, 0o5), # firstMoveLidFlip2 + datetime.datetime( 2010, Month.MAY, 20, 11, 0o5), # firstMoveJump trashcans around trashcan zero animate + datetime.datetime( 2010, Month.MAY, 23, 11, 0o5), # firstMoveLidFlip3 + datetime.datetime( 2010, Month.MAY, 29, 14, 10), # firstMoveJumpHit Trashcan Zero and his trashcan pals get more elaborate animations + datetime.datetime( 2010, Month.JUNE, 6, 14, 0o1), # firstMoveJumpJuggle + ], + }, + + ToontownGlobals.MAILBOX_ZERO_HOLIDAY: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.MAY, 9, 12, 00), # firstMoveFlagSpin1 + datetime.datetime( 2010, Month.JUNE, 12, 11, 55),], + 'phaseDates': [datetime.datetime( 2010, Month.MAY, 16, 16, 55), # firstMoveStruggle & Jump + datetime.datetime( 2010, Month.MAY, 21, 16, 55), # firstMoveFlagSpin2 + datetime.datetime( 2010, Month.MAY, 23, 17, 0o5), # firstMoveFlagSpin3 mailboxs around mailbox zero animate + datetime.datetime( 2010, Month.JUNE, 1, 11, 0o5), # firstMoveJumpSummersault + datetime.datetime( 2010, Month.JUNE, 5, 12, 0o1), # firstMoveJumpFall Mailbox Zero and his mailbox pals get more elaborate animations + datetime.datetime( 2010, Month.JUNE, 8, 11, 45), # firstMoveJump3Summersaults + ], + }, + + ToontownGlobals.SILLYMETER_HOLIDAY: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.MAY, 14, 0, 0o1), # Stage 1 animates + datetime.datetime( 2010, Month.JULY, 14, 0, 0o1),], + 'phaseDates': [datetime.datetime( 2010, Month.MAY, 17, 16, 0o1), # Stage 1 animates, stage 2 built + datetime.datetime( 2010, Month.MAY, 19, 00, 0o1), # Stage 1 loc 2 + datetime.datetime( 2010, Month.MAY, 22, 14, 0o1), # Stage 1 loc 3 + datetime.datetime( 2010, Month.MAY, 24, 17, 0o1), # Stage 1 loc 4 + + datetime.datetime( 2010, Month.MAY, 26, 00, 0o1), # Stage 2 loc 5 + datetime.datetime( 2010, Month.MAY, 30, 10, 0o1), # Stage 2 loc 6 + + datetime.datetime( 2010, Month.JUNE, 2, 0, 0o1), # Stage 3 is added and animates + datetime.datetime( 2010, Month.JUNE, 5, 12, 00), # Stage 3 loc 8 + datetime.datetime( 2010, Month.JUNE, 8, 10, 0o1), # Stage 3 loc 9 + + datetime.datetime( 2010, Month.JUNE, 9, 00, 0o1), # Stage 4 animates + datetime.datetime( 2010, Month.JUNE, 12, 10, 0o1), # Stage 4 loc 11 + datetime.datetime( 2010, Month.JUNE, 12, 12, 0o1), # Stage 4 loc 12 + + datetime.datetime( 2010, Month.JUNE, 13, 13, 30), # Stage 5 silly meter plummets + + datetime.datetime( 2010, Month.JUNE, 14, 0, 0o1), # Scientist chatter change + + datetime.datetime( 2010, Month.JUNE, 28, 0, 0o1), # Silly meter shuts down + ], + }, + + ToontownGlobals.SILLY_SURGE_HOLIDAY: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.MAY, 14, 0, 0o1), + datetime.datetime( 2010, Month.JUNE, 13, 13, 30), ], # Cogs invade and silly surges fizzle out + }, + + ToontownGlobals.TROUBLE_BOSSBOTS_4: # Down sizer + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.FEBRUARY, 1, 18, 0), + datetime.datetime( 2009, Month.FEBRUARY, 1, 23, 0), ], + }, + + ToontownGlobals.DOWN_SIZER_INVASION: # Down sizer + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 13, 13, 30), + datetime.datetime( 2010, Month.JUNE, 13, 17, 30), ], + }, + + ToontownGlobals.SELLBOT_SURPRISE_4: # Mover & shaker + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.JANUARY, 11, 18, 0), + datetime.datetime( 2009, Month.JANUARY, 11, 23, 0), ], + }, + + ToontownGlobals.MOVER_AND_SHAKER_INVASION: # Mover & shaker + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 13, 18, 30), + datetime.datetime( 2010, Month.JUNE, 13, 22, 30), ], + }, + + ToontownGlobals.LAWBOT_GAMBIT_2: # Double talker + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.JANUARY, 24, 18, 0), + datetime.datetime( 2009, Month.JANUARY, 24, 23, 0),], + }, + + ToontownGlobals.DOUBLETALKER_INVASION: # Double talker + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 14, 2, 00), + datetime.datetime( 2010, Month.JUNE, 14, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 14, 10, 00), + datetime.datetime( 2010, Month.JUNE, 14, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 14, 18, 00), + datetime.datetime( 2010, Month.JUNE, 14, 22, 00),], + }, + + ToontownGlobals.YES_MAN_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 15, 2, 00), + datetime.datetime( 2010, Month.JUNE, 15, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 15, 10, 00), + datetime.datetime( 2010, Month.JUNE, 15, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 15, 18, 00), + datetime.datetime( 2010, Month.JUNE, 15, 22, 00),], + }, + + ToontownGlobals.CASHBOT_CONUNDRUM_2: # Penny Pincher + { 'startAndEndPairs' : [ + datetime.datetime( 2009, Month.JANUARY, 17, 18, 00), + datetime.datetime( 2009, Month.JANUARY, 17, 23, 00),], + }, + + ToontownGlobals.PENNY_PINCHER_INVASION: # Penny Pincher + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 16, 2, 00), + datetime.datetime( 2010, Month.JUNE, 16, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 16, 10, 00), + datetime.datetime( 2010, Month.JUNE, 16, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 16, 18, 00), + datetime.datetime( 2010, Month.JUNE, 16, 22, 00),], + }, + + ToontownGlobals.TIGHTWAD_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 17, 2, 00), + datetime.datetime( 2010, Month.JUNE, 17, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 17, 10, 00), + datetime.datetime( 2010, Month.JUNE, 17, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 17, 18, 00), + datetime.datetime( 2010, Month.JUNE, 17, 22, 00),], + }, + + ToontownGlobals.TELEMARKETER_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 18, 2, 00), + datetime.datetime( 2010, Month.JUNE, 18, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 18, 10, 00), + datetime.datetime( 2010, Month.JUNE, 18, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 18, 18, 00), + datetime.datetime( 2010, Month.JUNE, 18, 22, 00),], + }, + + ToontownGlobals.HEADHUNTER_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 19, 2, 00), + datetime.datetime( 2010, Month.JUNE, 19, 4, 59), + + datetime.datetime( 2010, Month.JUNE, 19, 10, 00), + datetime.datetime( 2010, Month.JUNE, 19, 12, 59), + + datetime.datetime( 2010, Month.JUNE, 19, 18, 00), + datetime.datetime( 2010, Month.JUNE, 19, 20, 59),], + }, + + ToontownGlobals.SPINDOCTOR_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 19, 5, 00), + datetime.datetime( 2010, Month.JUNE, 19, 8, 00), + + datetime.datetime( 2010, Month.JUNE, 19, 13, 00), + datetime.datetime( 2010, Month.JUNE, 19, 16, 00), + + datetime.datetime( 2010, Month.JUNE, 19, 21, 00), + datetime.datetime( 2010, Month.JUNE, 19, 23, 59),], + }, + + ToontownGlobals.MONEYBAGS_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 20, 2, 00), + datetime.datetime( 2010, Month.JUNE, 20, 4, 59), + + datetime.datetime( 2010, Month.JUNE, 20, 10, 00), + datetime.datetime( 2010, Month.JUNE, 20, 12, 59), + + datetime.datetime( 2010, Month.JUNE, 20, 18, 00), + datetime.datetime( 2010, Month.JUNE, 20, 20, 59),], + }, + + ToontownGlobals.TWOFACES_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 20, 5, 00), + datetime.datetime( 2010, Month.JUNE, 20, 8, 00), + + datetime.datetime( 2010, Month.JUNE, 20, 13, 00), + datetime.datetime( 2010, Month.JUNE, 20, 16, 00), + + datetime.datetime( 2010, Month.JUNE, 20, 21, 00), + datetime.datetime( 2010, Month.JUNE, 20, 23, 59),], + }, + + ToontownGlobals.SELLBOT_SURPRISE_2: # Name dropper + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.JANUARY, 10, 18, 00), + datetime.datetime( 2009, Month.JANUARY, 10, 23, 00),], + }, + + ToontownGlobals.NAME_DROPPER_INVASION: # Name dropper + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 21, 2, 00), + datetime.datetime( 2010, Month.JUNE, 21, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 21, 10, 00), + datetime.datetime( 2010, Month.JUNE, 21, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 21, 18, 00), + datetime.datetime( 2010, Month.JUNE, 21, 22, 00),], + }, + + ToontownGlobals.TROUBLE_BOSSBOTS_3: # Micromanager + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.FEBRUARY, 10, 0, 00), + datetime.datetime( 2009, Month.FEBRUARY, 15, 0, 00),], + }, + + ToontownGlobals.MICROMANAGER_INVASION: # Micromanager + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 22, 2, 00), + datetime.datetime( 2010, Month.JUNE, 22, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 22, 10, 00), + datetime.datetime( 2010, Month.JUNE, 22, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 22, 18, 00), + datetime.datetime( 2010, Month.JUNE, 22, 22, 00),], + }, + + ToontownGlobals.CASHBOT_CONUNDRUM_4: # Number cruncher + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.JANUARY, 18, 18, 00), + datetime.datetime( 2009, Month.JANUARY, 18, 23, 00),], + }, + + ToontownGlobals.NUMBER_CRUNCHER_INVASION: # Number cruncher + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 23, 2, 00), + datetime.datetime( 2010, Month.JUNE, 23, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 23, 10, 00), + datetime.datetime( 2010, Month.JUNE, 23, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 23, 18, 00), + datetime.datetime( 2010, Month.JUNE, 23, 22, 00),], + }, + + ToontownGlobals.LAWBOT_GAMBIT_3: # Ambulance chaser + { 'startAndEndPairs' : [datetime.datetime( 2009, Month.JANUARY, 25, 10, 00), + datetime.datetime( 2009, Month.JANUARY, 25, 15, 00),], + }, + + ToontownGlobals.AMBULANCE_CHASER_INVASION: # Ambulance chaser + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 24, 2, 00), + datetime.datetime( 2010, Month.JUNE, 24, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 24, 10, 00), + datetime.datetime( 2010, Month.JUNE, 24, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 24, 18, 00), + datetime.datetime( 2010, Month.JUNE, 24, 22, 00),], + }, + + ToontownGlobals.MINGLER_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 25, 2, 00), + datetime.datetime( 2010, Month.JUNE, 25, 4, 59), + + datetime.datetime( 2010, Month.JUNE, 25, 10, 00), + datetime.datetime( 2010, Month.JUNE, 25, 12, 59), + + datetime.datetime( 2010, Month.JUNE, 25, 18, 00), + datetime.datetime( 2010, Month.JUNE, 25, 20, 59),], + }, + + ToontownGlobals.LOANSHARK_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 25, 5, 00), + datetime.datetime( 2010, Month.JUNE, 25, 8, 00), + + datetime.datetime( 2010, Month.JUNE, 25, 13, 00), + datetime.datetime( 2010, Month.JUNE, 25, 16, 00), + + datetime.datetime( 2010, Month.JUNE, 25, 21, 00), + datetime.datetime( 2010, Month.JUNE, 25, 23, 59),], + }, + + ToontownGlobals.CORPORATE_RAIDER_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 26, 2, 00), + datetime.datetime( 2010, Month.JUNE, 26, 4, 59), + + datetime.datetime( 2010, Month.JUNE, 26, 10, 00), + datetime.datetime( 2010, Month.JUNE, 26, 12, 59), + + datetime.datetime( 2010, Month.JUNE, 26, 18, 00), + datetime.datetime( 2010, Month.JUNE, 26, 20, 59),], + }, + + ToontownGlobals.LEGAL_EAGLE_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 26, 5, 00), + datetime.datetime( 2010, Month.JUNE, 26, 8, 00), + + datetime.datetime( 2010, Month.JUNE, 26, 13, 00), + datetime.datetime( 2010, Month.JUNE, 26, 16, 00), + + datetime.datetime( 2010, Month.JUNE, 26, 21, 00), + datetime.datetime( 2010, Month.JUNE, 26, 23, 59),], + }, + + ToontownGlobals.MR_HOLLYWOOD_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 27, 0, 00), + datetime.datetime( 2010, Month.JUNE, 27, 2, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 8, 00), + datetime.datetime( 2010, Month.JUNE, 27, 10, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 16, 00), + datetime.datetime( 2010, Month.JUNE, 27, 18, 00),], + }, + + ToontownGlobals.ROBBER_BARON_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 27, 2, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 4, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 10, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 12, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 18, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 20, 00),], + }, + + ToontownGlobals.BIG_WIG_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 27, 4, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 6, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 12, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 14, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 20, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 22, 00),], + }, + + ToontownGlobals.BIG_CHEESE_INVASION: + { 'startAndEndPairs' : [datetime.datetime( 2010, Month.JUNE, 27, 6, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 8, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 14, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 16, 00), + + datetime.datetime( 2010, Month.JUNE, 27, 22, 0o1), + datetime.datetime( 2010, Month.JUNE, 27, 23, 59),], + }, + + ToontownGlobals.HYDRANTS_BUFF_BATTLES: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.JUNE, 12, 12, 0o1), # they just animate but don't help + datetime.datetime( 2031, Month.JUNE, 7, 3, 0),], + 'phaseDates': [datetime.datetime( 2010, Month.JUNE, 14, 3, 0),], # they're actually helping now + }, + + ToontownGlobals.MAILBOXES_BUFF_BATTLES: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.JUNE, 12, 12, 0o1), # they just animate but don't help + datetime.datetime( 2031, Month.JUNE, 11, 3, 0),], #forever, impressive if we hit this! + 'phaseDates': [datetime.datetime( 2010, Month.JUNE, 18, 00, 0o1),], # they're actually helping now + }, + + ToontownGlobals.TRASHCANS_BUFF_BATTLES: + { 'startAndEndPairs': [datetime.datetime( 2010, Month.JUNE, 12, 12, 0o1), # they just animate but don't help + datetime.datetime( 2031, Month.JUNE, 11, 3, 0), ], #forever, impressive if we hit this! + 'phaseDates': [datetime.datetime( 2010, Month.JUNE, 18, 00, 0o1),], # they're actually helping now + }, + +} + +AdjustedHolidays = {} + +def adjustHolidaysForTestServer(): + for holidayId in OriginalHolidays: + AdjustedHolidays[holidayId] = {'startAndEndPairs':[], 'phaseDates': []} + newStartAndEndPairs = [] + + for curDate in OriginalHolidays[holidayId]['startAndEndPairs']: + adjusted = curDate - TestServerHolidayTimeDelta + newStartAndEndPairs.append((adjusted.year, adjusted.month, adjusted.day, adjusted.hour, adjusted.minute, adjusted.second)) + AdjustedHolidays[holidayId]['startAndEndPairs'] = newStartAndEndPairs + newPhaseDates = [] + if 'phaseDates' in OriginalHolidays[holidayId]: + for curDate in OriginalHolidays[holidayId]['phaseDates']: + adjusted = curDate - TestServerHolidayTimeDelta + newPhaseDates.append((adjusted.year, adjusted.month, adjusted.day, + adjusted.hour, adjusted.minute, adjusted.second)) + AdjustedHolidays[holidayId]['phaseDates'] = newPhaseDates + +adjustHolidaysForTestServer() +# TODO put this in a notify? although it should be an info if done so +print("AdjustedHolidays = %s" % AdjustedHolidays) class HolidayManagerAI: notify = DirectNotifyGlobal.directNotify.newCategory('HolidayManagerAI') + # { Month: [days] }, (startTime), (endTime)] + + holidaysCommon = { + ToontownGlobals.NEWYEARS_FIREWORKS: HolidayInfo_Yearly( + FireworkManagerAI.FireworkManagerAI, + [(Month.DECEMBER, 31, 0, 30, 0), + (Month.JANUARY, 2, 0, 30, 0)], + displayOnCalendar = True, + ), + +# ToontownGlobals.SKELECOG_INVASION: HolidayInfo_Yearly( +# HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, +# [(Month.APRIL, 15, 10, 0, 0), # 10am-3pm PST, 1pm-6pm EST +# (Month.APRIL, 15, 15, 0, 0), + +# (Month.APRIL, 15, 18, 0, 0), # 6pm-11pm PST, 9pm-2am EST +# (Month.APRIL, 15, 23, 0, 0)], +# displayOnCalendar = True, +# ), + +# ToontownGlobals.MR_HOLLYWOOD_INVASION: HolidayInfo_Yearly( +# HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, +# [(Month.MAY, 24, 0, 0, 1), +# (Month.MAY, 24, 8, 59, 59), +# (Month.MAY, 24, 15, 0, 1), +# (Month.MAY, 24, 23, 59, 59), + +# (Month.MAY, 25, 6, 0, 1), +# (Month.MAY, 25, 14, 59, 59), +# (Month.MAY, 25, 21, 0, 1), +# (Month.MAY, 26, 5, 59, 59), + +# (Month.MAY, 26, 12, 0, 1), +# (Month.MAY, 26, 20, 59, 59)], +# displayOnCalendar = True, +# ), + + ToontownGlobals.HALLOWEEN: HolidayInfo_Yearly( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (Month.OCTOBER, 31, 0o2, 0, 0), # 2am-6am PST + (Month.OCTOBER, 31, 0o7, 0, 0), + + (Month.OCTOBER, 31, 10, 0, 0), # 10am-3pm PST, 1pm-6pm EST + (Month.OCTOBER, 31, 15, 0, 0), + + (Month.OCTOBER, 31, 18, 0, 0), # 6pm-10pm PST, 9pm-1am EST + (Month.OCTOBER, 31, 23, 0, 0), + + (Month.NOVEMBER, 1, 0o2, 0, 0), # 2am-6am PST + (Month.NOVEMBER, 1, 0o7, 0, 0), + + (Month.NOVEMBER, 1, 10, 0, 0), # 10am-2pm PST, 1pm-5pm EST + (Month.NOVEMBER, 1, 15, 0, 0), + + (Month.NOVEMBER, 1, 18, 0, 0), # 6pm-10pm PST, 9pm-1am EST + (Month.NOVEMBER, 1, 23, 0, 0)], + displayOnCalendar = True, + ), + + #To occur at the same time as Halloween + ToontownGlobals.HALLOWEEN_PROPS: HolidayInfo_Yearly( + None, + [(Month.OCTOBER, 20, 0, 0, 1), + (Month.NOVEMBER, 1, 23, 59, 59), + ], + displayOnCalendar = False, + ), + + # Valentines Day + ToontownGlobals.VALENTINES_DAY: HolidayInfo_Yearly( + ValentinesDayMgrAI.ValentinesDayMgrAI, + [(Month.FEBRUARY, 8, 0, 0, 1), + (Month.FEBRUARY, 16, 23, 59, 59), + ], + displayOnCalendar = True, + ), + + #To occur at the same time as april fools 2009 + ToontownGlobals.CRASHED_LEADERBOARD: HolidayInfo_Oncely( + None, + [(2009, Month.APRIL, 1, 0, 0, 1), + (2009, Month.MAY, 21, 23, 58, 59), + ], + displayOnCalendar = False, + ), + + #To occur at the same time as Halloween + #TODO: better way to have intervals with holiday events + #instead of defining it per hour each day + ToontownGlobals.HALLOWEEN_COSTUMES: HolidayInfo_Yearly( + CostumeManagerAI.CostumeManagerAI, + [(Month.OCTOBER, 27, 0, 0, 1), # 12am-1am PST, 3am-4am EST + (Month.OCTOBER, 27, 0, 59, 59), + + (Month.OCTOBER, 27, 2, 0, 1), + (Month.OCTOBER, 27, 2, 59, 59), + + (Month.OCTOBER, 27, 4, 0, 1), + (Month.OCTOBER, 27, 4, 59, 59), + + (Month.OCTOBER, 27, 6, 0, 1), + (Month.OCTOBER, 27, 6, 59, 59), + + (Month.OCTOBER, 27, 8, 0, 1), + (Month.OCTOBER, 27, 8, 59, 59), + + (Month.OCTOBER, 27, 10, 0, 1), + (Month.OCTOBER, 27, 10, 59, 59), + + (Month.OCTOBER, 27, 12, 0, 1), # 12pm-1pm PST, 3pm-4pm EST + (Month.OCTOBER, 27, 12, 59, 59), + + (Month.OCTOBER, 27, 14, 0, 1), + (Month.OCTOBER, 27, 14, 59, 59), + + (Month.OCTOBER, 27, 16, 0, 1), + (Month.OCTOBER, 27, 16, 59, 59), + + (Month.OCTOBER, 27, 18, 0, 1), + (Month.OCTOBER, 27, 18, 59, 59), + + (Month.OCTOBER, 27, 20, 0, 1), + (Month.OCTOBER, 27, 20, 59, 59), + + (Month.OCTOBER, 27, 22, 0, 1), + (Month.OCTOBER, 27, 22, 59, 59), + + (Month.OCTOBER, 28, 0, 0, 1), # 12am-1am PST, 3am-4am EST + (Month.OCTOBER, 28, 0, 59, 59), + + (Month.OCTOBER, 28, 2, 0, 1), + (Month.OCTOBER, 28, 2, 59, 59), + + (Month.OCTOBER, 28, 4, 0, 1), + (Month.OCTOBER, 28, 4, 59, 59), + + (Month.OCTOBER, 28, 6, 0, 1), + (Month.OCTOBER, 28, 6, 59, 59), + + (Month.OCTOBER, 28, 8, 0, 1), + (Month.OCTOBER, 28, 8, 59, 59), + + (Month.OCTOBER, 28, 10, 0, 1), + (Month.OCTOBER, 28, 10, 59, 59), + + (Month.OCTOBER, 28, 12, 0, 1), # 12pm-1pm PST, 3pm-4pm EST + (Month.OCTOBER, 28, 12, 59, 59), + + (Month.OCTOBER, 28, 14, 0, 1), + (Month.OCTOBER, 28, 14, 59, 59), + + (Month.OCTOBER, 28, 16, 0, 1), + (Month.OCTOBER, 28, 16, 59, 59), + + (Month.OCTOBER, 28, 18, 0, 1), + (Month.OCTOBER, 28, 18, 59, 59), + + (Month.OCTOBER, 28, 20, 0, 1), + (Month.OCTOBER, 28, 20, 59, 59), + + (Month.OCTOBER, 28, 22, 0, 1), + (Month.OCTOBER, 28, 22, 59, 59), + + (Month.OCTOBER, 29, 0, 0, 1), # 12am-1am PST, 3am-4am EST + (Month.OCTOBER, 29, 0, 59, 59), + + (Month.OCTOBER, 29, 2, 0, 1), + (Month.OCTOBER, 29, 2, 59, 59), + + (Month.OCTOBER, 29, 4, 0, 1), + (Month.OCTOBER, 29, 4, 59, 59), + + (Month.OCTOBER, 29, 6, 0, 1), + (Month.OCTOBER, 29, 6, 59, 59), + + (Month.OCTOBER, 29, 8, 0, 1), + (Month.OCTOBER, 29, 8, 59, 59), + + (Month.OCTOBER, 29, 10, 0, 1), + (Month.OCTOBER, 29, 10, 59, 59), + + (Month.OCTOBER, 29, 12, 0, 1), # 12pm-1pm PST, 3pm-4pm EST + (Month.OCTOBER, 29, 12, 59, 59), + + (Month.OCTOBER, 29, 14, 0, 1), + (Month.OCTOBER, 29, 14, 59, 59), + + (Month.OCTOBER, 29, 16, 0, 1), + (Month.OCTOBER, 29, 16, 59, 59), + + (Month.OCTOBER, 29, 18, 0, 1), + (Month.OCTOBER, 29, 18, 59, 59), + + (Month.OCTOBER, 29, 20, 0, 1), + (Month.OCTOBER, 29, 20, 59, 59), + + (Month.OCTOBER, 29, 22, 0, 1), + (Month.OCTOBER, 29, 22, 59, 59), + + (Month.OCTOBER, 30, 0, 0, 1), # 12am-1am PST, 3am-4am EST + (Month.OCTOBER, 30, 0, 59, 59), + + (Month.OCTOBER, 30, 2, 0, 1), + (Month.OCTOBER, 30, 2, 59, 59), + + (Month.OCTOBER, 30, 4, 0, 1), + (Month.OCTOBER, 30, 4, 59, 59), + + (Month.OCTOBER, 30, 6, 0, 1), + (Month.OCTOBER, 30, 6, 59, 59), + + (Month.OCTOBER, 30, 8, 0, 1), + (Month.OCTOBER, 30, 8, 59, 59), + + (Month.OCTOBER, 30, 10, 0, 1), + (Month.OCTOBER, 30, 10, 59, 59), + + (Month.OCTOBER, 30, 12, 0, 1), # 12pm-1pm PST, 3pm-4pm EST + (Month.OCTOBER, 30, 12, 59, 59), + + (Month.OCTOBER, 30, 14, 0, 1), + (Month.OCTOBER, 30, 14, 59, 59), + + (Month.OCTOBER, 30, 16, 0, 1), + (Month.OCTOBER, 30, 16, 59, 59), + + (Month.OCTOBER, 30, 18, 0, 1), + (Month.OCTOBER, 30, 18, 59, 59), + + (Month.OCTOBER, 30, 20, 0, 1), + (Month.OCTOBER, 30, 20, 59, 59), + + (Month.OCTOBER, 30, 22, 0, 1), + (Month.OCTOBER, 30, 22, 59, 59), + + (Month.OCTOBER, 31, 0, 0, 1), # 12am-1am PST, 3am-4am EST + (Month.OCTOBER, 31, 0, 59, 59), + + (Month.OCTOBER, 31, 2, 0, 1), + (Month.OCTOBER, 31, 2, 59, 59), + + (Month.OCTOBER, 31, 4, 0, 1), + (Month.OCTOBER, 31, 4, 59, 59), + + (Month.OCTOBER, 31, 6, 0, 1), + (Month.OCTOBER, 31, 6, 59, 59), + + (Month.OCTOBER, 31, 8, 0, 1), + (Month.OCTOBER, 31, 8, 59, 59), + + (Month.OCTOBER, 31, 10, 0, 1), + (Month.OCTOBER, 31, 10, 59, 59), + + (Month.OCTOBER, 31, 12, 0, 1), # 12pm-1pm PST, 3pm-4pm EST + (Month.OCTOBER, 31, 12, 59, 59), + + (Month.OCTOBER, 31, 14, 0, 1), + (Month.OCTOBER, 31, 14, 59, 59), + + (Month.OCTOBER, 31, 16, 0, 1), + (Month.OCTOBER, 31, 16, 59, 59), + + (Month.OCTOBER, 31, 18, 0, 1), + (Month.OCTOBER, 31, 18, 59, 59), + + (Month.OCTOBER, 31, 20, 0, 1), + (Month.OCTOBER, 31, 20, 59, 59), + + (Month.OCTOBER, 31, 22, 0, 1), + (Month.OCTOBER, 31, 22, 59, 59), + + (Month.NOVEMBER, 1, 0, 0, 1), # 12am-1am PST, 3am-4am EST + (Month.NOVEMBER, 1, 0, 59, 59), + + (Month.NOVEMBER, 1, 2, 0, 1), + (Month.NOVEMBER, 1, 2, 59, 59), + + (Month.NOVEMBER, 1, 4, 0, 1), + (Month.NOVEMBER, 1, 4, 59, 59), + + (Month.NOVEMBER, 1, 6, 0, 1), + (Month.NOVEMBER, 1, 6, 59, 59), + + (Month.NOVEMBER, 1, 8, 0, 1), + (Month.NOVEMBER, 1, 8, 59, 59), + + (Month.NOVEMBER, 1, 10, 0, 1), + (Month.NOVEMBER, 1, 10, 59, 59), + + (Month.NOVEMBER, 1, 12, 0, 1), # 12pm-1pm PST, 3pm-4pm EST + (Month.NOVEMBER, 1, 12, 59, 59), + + (Month.NOVEMBER, 1, 14, 0, 1), + (Month.NOVEMBER, 1, 14, 59, 59), + + (Month.NOVEMBER, 1, 16, 0, 1), + (Month.NOVEMBER, 1, 16, 59, 59), + + (Month.NOVEMBER, 1, 18, 0, 1), + (Month.NOVEMBER, 1, 18, 59, 59), + + (Month.NOVEMBER, 1, 20, 0, 1), + (Month.NOVEMBER, 1, 20, 59, 59), + + (Month.NOVEMBER, 1, 22, 0, 1), + (Month.NOVEMBER, 1, 23, 59, 59),], + displayOnCalendar = False, + ), + + ToontownGlobals.APRIL_FOOLS_COSTUMES: HolidayInfo_Yearly( + AprilFoolsManagerAI.AprilFoolsManagerAI, + [(Month.MARCH, 31, 0, 0, 1), + (Month.APRIL, 7, 23, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.BLACK_CAT_DAY: HolidayInfo_Yearly( + BlackCatHolidayMgrAI.BlackCatHolidayMgrAI, + #[(Month.SEPTEMBER, 31, 0, 0, 1), + # (Month.NOVEMBER, 31, 23, 59, 59)] + [(Month.OCTOBER, 31, 0, 0, 1), + (Month.OCTOBER, 31, 23, 59, 59)], + displayOnCalendar = True, + ), + + # Winter Decorations - runs for fifteen days. + # time1: 12:01am PST on December 19th to 11:59pm PST on January 2nd + ToontownGlobals.WINTER_DECORATIONS: HolidayInfo_Yearly( + None, + [(Month.DECEMBER, 8, 0, 0, 1), + (Month.JANUARY, 3, 23, 58, 00)], + displayOnCalendar = True, + ), + + ToontownGlobals.MORE_XP_HOLIDAY: HolidayInfo_Oncely( + None, + # Double XP Holiday, set in the future to be manually triggered + [(2029, Month.JANUARY, 1, 0, 0, 1), + (2029, Month.JANUARY, 1, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.HYDRANT_ZERO_HOLIDAY: HolidayInfo_Oncely( + HydrantZeroHolidayAI.HydrantZeroHolidayAI, + # Hydrant zero animating + AdjustedHolidays[ToontownGlobals.HYDRANT_ZERO_HOLIDAY]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.HYDRANT_ZERO_HOLIDAY]['phaseDates'], + ), + + ToontownGlobals.MAILBOX_ZERO_HOLIDAY: HolidayInfo_Oncely( + MailboxZeroHolidayAI.MailboxZeroHolidayAI, + # Mailbox zero animating + AdjustedHolidays[ToontownGlobals.MAILBOX_ZERO_HOLIDAY]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.MAILBOX_ZERO_HOLIDAY]['phaseDates'], + ), + + ToontownGlobals.TRASHCAN_ZERO_HOLIDAY: HolidayInfo_Oncely( + TrashcanZeroHolidayAI.TrashcanZeroHolidayAI, + # Trashcan zero animating + AdjustedHolidays[ToontownGlobals.TRASHCAN_ZERO_HOLIDAY]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.TRASHCAN_ZERO_HOLIDAY]['phaseDates'], + ), + + ToontownGlobals.SILLYMETER_HOLIDAY: HolidayInfo_Oncely( + SillyMeterHolidayAI.SillyMeterHolidayAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.SILLYMETER_HOLIDAY]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.SILLYMETER_HOLIDAY]['phaseDates'], + ), + + ToontownGlobals.SILLY_SURGE_HOLIDAY: HolidayInfo_Oncely( + None, + # Silly Surge text appearing when cog gets damaged + AdjustedHolidays[ToontownGlobals.SILLY_SURGE_HOLIDAY]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.SILLY_SURGE_HOLIDAY]['phaseDates'], + ), + + ToontownGlobals.SILLY_CHATTER_ONE: HolidayInfo_Oncely( + None, + [(2010, Month.MAY, 14, 0, 0, 1), + (2010, Month.MAY, 25, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.SILLY_CHATTER_TWO: HolidayInfo_Oncely( + None, + [(2010, Month.MAY, 26, 0, 0, 1), + (2010, Month.JUNE, 1, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.SILLY_CHATTER_THREE: HolidayInfo_Oncely( + None, + [(2010, Month.JUNE, 2, 0, 0, 1), + (2010, Month.JUNE, 17, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.SILLY_CHATTER_FOUR: HolidayInfo_Oncely( + None, + [(2010, Month.JUNE, 18, 0, 0, 1), + (2010, Month.JUNE, 27, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.SILLY_CHATTER_FIVE: HolidayInfo_Oncely( + None, + [(2010, Month.JUNE, 28, 0, 0, 1), + (2010, Month.JULY, 13, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.SILLY_TEST : HolidayInfo_Oncely( + HolidayRepeaterAI.HolidayRepeaterAI, + [(2010, Month.APRIL, 2 , 0, 0, 1), + (2020, Month.APRIL, 2, 0, 0, 1)], + displayOnCalendar = False, + testHolidays = { ToontownGlobals.SILLYMETER_HOLIDAY : [60, 90, 110, 150, 190, 200, 220, 240, 260, 290, 310, 320, 330, 340, 420, 610, 620], \ + ToontownGlobals.SILLY_CHATTER_ONE : [60, 195] , \ + ToontownGlobals.SILLY_CHATTER_TWO : [200, 235], ToontownGlobals.SILLY_CHATTER_THREE : [240, 325], \ + ToontownGlobals.SILLY_CHATTER_FOUR : [330, 610], \ + ToontownGlobals.HYDRANT_ZERO_HOLIDAY : [0, 20, 50, 100, 130, 160, 250, 325], \ + ToontownGlobals.MAILBOX_ZERO_HOLIDAY : [30, 80, 140, 180, 230, 270, 300, 325], \ + ToontownGlobals.TRASHCAN_ZERO_HOLIDAY : [10, 40, 70, 120, 170, 210, 280, 325], \ + ToontownGlobals.SILLY_SURGE_HOLIDAY : [60, 340], \ + ToontownGlobals.HYDRANTS_BUFF_BATTLES : [330, 350], \ + ToontownGlobals.MAILBOXES_BUFF_BATTLES : [330, 420, ], + ToontownGlobals.TRASHCANS_BUFF_BATTLES : [330, 420, ], + ToontownGlobals.DOWN_SIZER_INVASION: [360, 369], + ToontownGlobals.MOVER_AND_SHAKER_INVASION: [370, 379], + ToontownGlobals.DOUBLETALKER_INVASION: [380, 389], + ToontownGlobals.YES_MAN_INVASION: [390, 399], + ToontownGlobals.PENNY_PINCHER_INVASION: [400, 409], + ToontownGlobals.TIGHTWAD_INVASION: [410, 419], + ToontownGlobals.TELEMARKETER_INVASION : [430, 439], + ToontownGlobals.HEADHUNTER_INVASION : [440, 449], + ToontownGlobals.SPINDOCTOR_INVASION : [450, 459], + ToontownGlobals.MONEYBAGS_INVASION : [460, 469], + ToontownGlobals.TWOFACES_INVASION : [470, 479], + ToontownGlobals.NAME_DROPPER_INVASION : [480, 489], + ToontownGlobals.MICROMANAGER_INVASION : [490, 499], + ToontownGlobals.NUMBER_CRUNCHER_INVASION : [500, 509], + ToontownGlobals.AMBULANCE_CHASER_INVASION : [510, 519], + ToontownGlobals.MINGLER_INVASION : [520, 529], + ToontownGlobals.LOANSHARK_INVASION : [530, 539], + ToontownGlobals.CORPORATE_RAIDER_INVASION : [540, 549], + ToontownGlobals.LEGAL_EAGLE_INVASION : [550, 559], + ToontownGlobals.MR_HOLLYWOOD_INVASION : [560, 569], + ToontownGlobals.ROBBER_BARON_INVASION : [570, 579], + ToontownGlobals.BIG_WIG_INVASION : [580, 589], + ToontownGlobals.BIG_CHEESE_INVASION : [590, 599], + }, + ), + + ToontownGlobals.HYDRANTS_BUFF_BATTLES: HolidayInfo_Oncely( + HydrantBuffHolidayAI.HydrantBuffHolidayAI, + AdjustedHolidays[ToontownGlobals.HYDRANTS_BUFF_BATTLES]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.HYDRANTS_BUFF_BATTLES]['phaseDates'], + ), + + ToontownGlobals.MAILBOXES_BUFF_BATTLES: HolidayInfo_Oncely( + MailboxBuffHolidayAI.MailboxBuffHolidayAI, + AdjustedHolidays[ToontownGlobals.MAILBOXES_BUFF_BATTLES]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.MAILBOXES_BUFF_BATTLES]['phaseDates'], + ), + + ToontownGlobals.TRASHCANS_BUFF_BATTLES: HolidayInfo_Oncely( + TrashcanBuffHolidayAI.TrashcanBuffHolidayAI, + AdjustedHolidays[ToontownGlobals.TRASHCANS_BUFF_BATTLES]['startAndEndPairs'], + displayOnCalendar = False, + phaseDates = AdjustedHolidays[ToontownGlobals.TRASHCANS_BUFF_BATTLES]['phaseDates'], + ), + } + + if not simbase.config.GetBool('want-silly-test', False): + del holidaysCommon[ToontownGlobals.SILLY_TEST] + + holidaysEnglish = { + ToontownGlobals.JULY4_FIREWORKS: HolidayInfo_Yearly( + FireworkManagerAI.FireworkManagerAI, + # Fourth of July Fireworks - for 16 days + # Time1: 12am PST on June 30th to 11:59pm PST on July 15th + [(Month.JUNE, 30, 0, 0, 1), + (Month.JULY, 15, 23, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.TRICK_OR_TREAT: HolidayInfo_Yearly( + TrickOrTreatMgrAI.TrickOrTreatMgrAI, + [(Month.OCTOBER, 27, 0, 0, 1), + (Month.NOVEMBER, 1, 23, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.WINTER_CAROLING: HolidayInfo_Yearly( + WinterCarolingMgrAI.WinterCarolingMgrAI, + [(Month.DECEMBER, 22, 0, 0, 1), + (Month.JANUARY, 1, 23, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.RESISTANCE_EVENT: HolidayInfo_Yearly( + ResistanceEventMgrAI.ResistanceEventMgrAI, + # HACK! TODO: make this last indefinately + [(Month.JANUARY, 1, 0, 0, 1), + (Month.DECEMBER, 31, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.POLAR_PLACE_EVENT: HolidayInfo_Yearly( + PolarPlaceEventMgrAI.PolarPlaceEventMgrAI, + # HACK! TODO: make this last indefinately + [(Month.JANUARY, 1, 0, 0, 1), + (Month.DECEMBER, 31, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.ELECTION_PROMOTION: HolidayInfo_Oncely( + None, + # Toon Election phrases + [(2007, Month.JANUARY, 1, 0, 0, 1), + (2007, Month.JANUARY, 22, 23, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.TROLLEY_WEEKEND : HolidayInfo_Oncely( + TrolleyWeekendMgrAI.TrolleyWeekendMgrAI, + [(2007, Month.APRIL, 14, 0, 0, 1), + (2007, Month.APRIL, 15, 23, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.BOSSCOG_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [(2007, Month.DECEMBER, 15, 0, 0, 1), + (2007, Month.DECEMBER, 15, 0, 59, 59), + (2007, Month.DECEMBER, 15, 2, 0, 1), + (2007, Month.DECEMBER, 15, 2, 59, 59), + (2007, Month.DECEMBER, 15, 4, 0, 1), + (2007, Month.DECEMBER, 15, 4, 59, 59), + (2007, Month.DECEMBER, 15, 6, 0, 1), + (2007, Month.DECEMBER, 15, 6, 59, 59), + (2007, Month.DECEMBER, 15, 8, 0, 1), + (2007, Month.DECEMBER, 15, 8, 59, 59), + (2007, Month.DECEMBER, 15, 10, 0, 1), + (2007, Month.DECEMBER, 15, 10, 59, 59), + (2007, Month.DECEMBER, 15, 12, 0, 1), + (2007, Month.DECEMBER, 15, 12, 59, 59), + (2007, Month.DECEMBER, 15, 14, 0, 1), + (2007, Month.DECEMBER, 15, 14, 59, 59), + (2007, Month.DECEMBER, 15, 16, 0, 1), + (2007, Month.DECEMBER, 15, 16, 59, 59), + (2007, Month.DECEMBER, 15, 18, 0, 1), + (2007, Month.DECEMBER, 15, 18, 59, 59), + (2007, Month.DECEMBER, 15, 20, 0, 1), + (2007, Month.DECEMBER, 15, 20, 59, 59), + (2007, Month.DECEMBER, 15, 22, 0, 1), + (2007, Month.DECEMBER, 15, 22, 59, 59), + + (2007, Month.DECEMBER, 16, 0, 0, 1), + (2007, Month.DECEMBER, 16, 0, 59, 59), + (2007, Month.DECEMBER, 16, 2, 0, 1), + (2007, Month.DECEMBER, 16, 2, 59, 59), + (2007, Month.DECEMBER, 16, 4, 0, 1), + (2007, Month.DECEMBER, 16, 4, 59, 59), + (2007, Month.DECEMBER, 16, 6, 0, 1), + (2007, Month.DECEMBER, 16, 6, 59, 59), + (2007, Month.DECEMBER, 16, 8, 0, 1), + (2007, Month.DECEMBER, 16, 8, 59, 59), + (2007, Month.DECEMBER, 16, 10, 0, 1), + (2007, Month.DECEMBER, 16, 10, 59, 59), + (2007, Month.DECEMBER, 16, 12, 0, 1), + (2007, Month.DECEMBER, 16, 12, 59, 59), + (2007, Month.DECEMBER, 16, 14, 0, 1), + (2007, Month.DECEMBER, 16, 14, 59, 59), + (2007, Month.DECEMBER, 16, 16, 0, 1), + (2007, Month.DECEMBER, 16, 16, 59, 59), + (2007, Month.DECEMBER, 16, 18, 0, 1), + (2007, Month.DECEMBER, 16, 18, 59, 59), + (2007, Month.DECEMBER, 16, 20, 0, 1), + (2007, Month.DECEMBER, 16, 20, 59, 59), + (2007, Month.DECEMBER, 16, 22, 0, 1), + (2007, Month.DECEMBER, 16, 22, 59, 59)], + displayOnCalendar = False, + ), + + ToontownGlobals.MARCH_INVASION: HolidayInfo_Yearly( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (Month.MARCH, 14, 2, 0, 1), + (Month.MARCH, 14, 4, 59, 59), + (Month.MARCH, 14, 10, 0, 1), + (Month.MARCH, 14, 12, 59, 59), + (Month.MARCH, 14, 18, 0, 1), + (Month.MARCH, 14, 20, 59, 59), + + (Month.MARCH, 15, 2, 0, 1), + (Month.MARCH, 15, 4, 59, 59), + (Month.MARCH, 15, 10, 0, 1), + (Month.MARCH, 15, 12, 59, 59), + (Month.MARCH, 15, 18, 0, 1), + (Month.MARCH, 15, 20, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.DECEMBER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2008, Month.DECEMBER, 27, 10, 0, 1), + (2008, Month.DECEMBER, 27, 14, 59, 59), + + (2008, Month.DECEMBER, 27, 18, 0, 1), + (2008, Month.DECEMBER, 27, 22, 59, 59), + + (2008, Month.DECEMBER, 28, 10, 0, 1), + (2008, Month.DECEMBER, 28, 14, 59, 59), + + (2008, Month.DECEMBER, 28, 18, 0, 1), + (2008, Month.DECEMBER, 28, 22, 59, 59)], + displayOnCalendar = True, + ), + + ToontownGlobals.SELLBOT_SURPRISE_1: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 10, 10, 0, 0), + (2009, Month.JANUARY, 10, 15, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.SELLBOT_SURPRISE_2: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.SELLBOT_SURPRISE_2]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.NAME_DROPPER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.NAME_DROPPER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.SELLBOT_SURPRISE_3: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 11, 10, 0, 0), + (2009, Month.JANUARY, 11, 15, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.SELLBOT_SURPRISE_4: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.SELLBOT_SURPRISE_4]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.MOVER_AND_SHAKER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.MOVER_AND_SHAKER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.MR_HOLLYWOOD_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.MR_HOLLYWOOD_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.MINGLER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.MINGLER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.TWOFACES_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.TWOFACES_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.TELEMARKETER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.TELEMARKETER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.HEADHUNTER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.HEADHUNTER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.CASHBOT_CONUNDRUM_1: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 17, 10, 0, 0), + (2009, Month.JANUARY, 17, 15, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.CASHBOT_CONUNDRUM_2: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.CASHBOT_CONUNDRUM_2]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.PENNY_PINCHER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.PENNY_PINCHER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.CASHBOT_CONUNDRUM_3: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 18, 10, 0, 0), + (2009, Month.JANUARY, 18, 15, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.CASHBOT_CONUNDRUM_4: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.CASHBOT_CONUNDRUM_4]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.NUMBER_CRUNCHER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.NUMBER_CRUNCHER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.ROBBER_BARON_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.ROBBER_BARON_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.LOANSHARK_INVASION : HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.LOANSHARK_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.MONEYBAGS_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.MONEYBAGS_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.TIGHTWAD_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.TIGHTWAD_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.LAWBOT_GAMBIT_1: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 24, 10, 0, 0), + (2009, Month.JANUARY, 24, 15, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.LAWBOT_GAMBIT_2: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.LAWBOT_GAMBIT_2]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.DOUBLETALKER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.DOUBLETALKER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.LAWBOT_GAMBIT_3: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.LAWBOT_GAMBIT_3]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.AMBULANCE_CHASER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.AMBULANCE_CHASER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.LAWBOT_GAMBIT_4: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 25, 18, 0, 0), + (2009, Month.JANUARY, 25, 23, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.LEGAL_EAGLE_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.LEGAL_EAGLE_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.SPINDOCTOR_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.SPINDOCTOR_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.TROUBLE_BOSSBOTS_1: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 31, 10, 0, 0), + (2009, Month.JANUARY, 31, 15, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.TROUBLE_BOSSBOTS_2: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.JANUARY, 31, 18, 0, 0), + (2009, Month.JANUARY, 31, 23, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.TROUBLE_BOSSBOTS_3: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.TROUBLE_BOSSBOTS_3]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.MICROMANAGER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + AdjustedHolidays[ToontownGlobals.MICROMANAGER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.TROUBLE_BOSSBOTS_4: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.TROUBLE_BOSSBOTS_4]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.DOWN_SIZER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.DOWN_SIZER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.CORPORATE_RAIDER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.CORPORATE_RAIDER_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.YES_MAN_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.YES_MAN_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.BIG_WIG_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.BIG_WIG_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.BIG_CHEESE_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + # Silly Meter animating + AdjustedHolidays[ToontownGlobals.BIG_CHEESE_INVASION]['startAndEndPairs'], + displayOnCalendar = True, + ), + + ToontownGlobals.JELLYBEAN_DAY: HolidayInfo_Yearly( + None, + [ (Month.APRIL, 22, 0, 0, 1), + (Month.APRIL, 22, 23, 59, 59), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.COLD_CALLER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.AUGUST, 21, 2, 0, 0), + (2009, Month.AUGUST, 21, 5, 0, 0), + + (2009, Month.AUGUST, 21, 10, 0, 0), + (2009, Month.AUGUST, 21, 13, 0, 0), + + (2009, Month.AUGUST, 21, 18, 0, 0), + (2009, Month.AUGUST, 21, 21, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.BEAN_COUNTER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.AUGUST, 22, 2, 0, 0), + (2009, Month.AUGUST, 22, 5, 0, 0), + + (2009, Month.AUGUST, 22, 10, 0, 0), + (2009, Month.AUGUST, 22, 13, 0, 0), + + (2009, Month.AUGUST, 22, 18, 0, 0), + (2009, Month.AUGUST, 22, 21, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.DOUBLE_TALKER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.AUGUST, 23, 2, 0, 0), + (2009, Month.AUGUST, 23, 5, 0, 0), + + (2009, Month.AUGUST, 23, 10, 0, 0), + (2009, Month.AUGUST, 23, 13, 0, 0), + + (2009, Month.AUGUST, 23, 18, 0, 0), + (2009, Month.AUGUST, 23, 21, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.DOWNSIZER_INVASION: HolidayInfo_Oncely( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (2009, Month.AUGUST, 24, 2, 0, 0), + (2009, Month.AUGUST, 24, 5, 0, 0), + + (2009, Month.AUGUST, 24, 10, 0, 0), + (2009, Month.AUGUST, 24, 13, 0, 0), + + (2009, Month.AUGUST, 24, 18, 0, 0), + (2009, Month.AUGUST, 24, 21, 0, 0), + ], + displayOnCalendar = True, + ), + + ToontownGlobals.VICTORY_PARTY_HOLIDAY: HolidayInfo_Oncely( + None, + [(2010, Month.JULY, 21, 0, 0, 1), + (2010, Month.AUGUST, 17, 23, 59, 59)], + displayOnCalendar = True, + ), + } + + + holidaysJapanese = { + ToontownGlobals.NEWYEARS_FIREWORKS: HolidayInfo_Yearly( + FireworkManagerAI.FireworkManagerAI, + [(Month.DECEMBER, 30, 6, 0, 0), + (Month.JANUARY, 1, 5, 0, 0) ], + displayOnCalendar = False, + ), + + ToontownGlobals.JULY4_FIREWORKS: HolidayInfo_Yearly( + FireworkManagerAI.FireworkManagerAI, + # 7pm-9pm JPN + [(Month.JULY, 23, 18, 0, 0), + (Month.JULY, 23, 20, 30, 0), + + (Month.JULY, 25, 18, 0, 0), + (Month.JULY, 25, 20, 30, 0), + + (Month.JULY, 30, 18, 0, 0), + (Month.JULY, 30, 20, 30, 0), + + (Month.JULY, 31, 18, 0, 0), + (Month.JULY, 31, 20, 30, 0), + + (Month.AUGUST, 4, 0o1, 0, 0), + (Month.AUGUST, 4, 0o5, 30, 0), + + (Month.AUGUST, 5, 0o1, 0, 0), + (Month.AUGUST, 5, 0o5, 30, 0), + + (Month.AUGUST, 6, 0o1, 0, 0), + (Month.AUGUST, 6, 0o5, 30, 0), + + (Month.AUGUST, 7, 0o1, 0, 0), + (Month.AUGUST, 7, 0o5, 30, 0), + + (Month.AUGUST, 8, 0o1, 0, 0), + (Month.AUGUST, 8, 0o5, 30, 0), + + (Month.AUGUST, 9, 0o1, 0, 0), + (Month.AUGUST, 9, 0o5, 30, 0), + + (Month.AUGUST, 10, 0o1, 0, 0), + (Month.AUGUST, 10, 0o5, 30, 0), + + (Month.AUGUST, 11, 0o1, 0, 0), + (Month.AUGUST, 11, 0o5, 30, 0)], + displayOnCalendar = False, + ), + + ToontownGlobals.WINTER_DECORATIONS: HolidayInfo_Yearly( + None, + [ (Month.NOVEMBER, 30, 6, 0, 0), + (Month.JANUARY, 14, 5, 0, 0) ], + displayOnCalendar = False, + ), + + ToontownGlobals.HALLOWEEN: HolidayInfo_Yearly( + HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI, + [ (Month.OCTOBER, 30, 17, 0, 0), # 10am-3pm PST, 1pm-6pm EST + (Month.OCTOBER, 30, 22, 0, 0), + + (Month.OCTOBER, 31, 1, 0, 0), # 6pm-11pm PST, 9pm-2am EST + (Month.OCTOBER, 31, 6, 0, 0) ], + displayOnCalendar = False, + ), + + ToontownGlobals.BLACK_CAT_DAY: HolidayInfo_Yearly( + BlackCatHolidayMgrAI.BlackCatHolidayMgrAI, + [ (Month.OCTOBER, 30, 7, 0, 1), + (Month.OCTOBER, 31, 6, 59, 59) ], + displayOnCalendar = False, + ), + + #ToontownGlobals.TRICK_OR_TREAT: HolidayInfo_Yearly( + #TrickOrTreatMgrAI.TrickOrTreatMgrAI, + #[ (Month.OCTOBER, 27, 7, 0, 1), + # (Month.OCTOBER, 30, 14, 59, 59) ] + #), + + ToontownGlobals.FISH_BINGO_NIGHT: HolidayInfo_Weekly( + BingoNightHolidayAI.BingoNightHolidayAI, + # Fish Bingo Night - runs once a week + # Time1: 3pm PST to 9pm PST on Wednesdays + [ (Day.TUESDAY, 20, 0, 0), + (Day.WEDNESDAY, 5, 0, 0), + + (Day.SATURDAY, 20, 0, 0), + (Day.SUNDAY, 5, 0, 0) ], + displayOnCalendar = False, + ), + + ToontownGlobals.KART_RECORD_DAILY_RESET: HolidayInfo_Daily( + RaceManagerAI.KartRecordDailyResetter, + [ (7, 24, 1), + (7, 24, 30), + ], + displayOnCalendar = False, + ), + ToontownGlobals.KART_RECORD_WEEKLY_RESET: HolidayInfo_Weekly( + RaceManagerAI.KartRecordWeeklyResetter, + [ (Day.SUNDAY, 7, 25, 1), + (Day.SUNDAY, 7, 25, 30), + ], + displayOnCalendar = False, + ), + ToontownGlobals.CIRCUIT_RACING: HolidayInfo_Weekly( + RaceManagerAI.CircuitRaceHolidayMgr, + [ (Day.MONDAY, 7, 0, 1), + (Day.TUESDAY, 6, 59, 59), + + (Day.FRIDAY, 7, 0, 1), + (Day.SATURDAY, 6, 59, 59), + ], + displayOnCalendar = False, + ), + ToontownGlobals.TROLLEY_HOLIDAY: HolidayInfo_Weekly( + TrolleyHolidayMgrAI.TrolleyHolidayMgrAI, + [ (Day.SATURDAY, 19, 0, 0), + (Day.SUNDAY, 6, 59, 59), + ], + displayOnCalendar = False, + ) + } + + holidaysGerman = { + ToontownGlobals.JULY4_FIREWORKS: HolidayInfo_Yearly( + FireworkManagerAI.FireworkManagerAI, + [(Month.OCTOBER, 3, 0, 0, 0), + # Stop them in the middle of the final hour so we do not interrupt a show in the middle + (Month.OCTOBER, 4, 0, 30, 0)], + displayOnCalendar = False, + ), + + ToontownGlobals.WINTER_DECORATIONS: HolidayInfo_Yearly( + None, # No class defined, we just want the news manager to be called + # 4pm December 1st - Midnight December 29th + [(Month.DECEMBER, 1, 0, 0, 0), + (Month.DECEMBER, 30, 0, 0, 0)], + displayOnCalendar = False, + ) + } + + holidaysPortuguese = { + + } + + holidaysFrench = { + ToontownGlobals.JULY4_FIREWORKS: HolidayInfo_Yearly( + FireworkManagerAI.FireworkManagerAI, + # Bastille Day + [(Month.JULY, 14, 0, 0, 0), + # Stop them in the middle of the final hour so we do not interrupt a show in the middle + (Month.JULY, 15, 0, 30, 0)], + displayOnCalendar = False, + ), + + ToontownGlobals.WINTER_DECORATIONS: HolidayInfo_Yearly( + None, # No class defined, we just want the news manager to be called + # 4pm December 1st - Midnight December 29th + [(Month.DECEMBER, 1, 0, 0, 0), + (Month.DECEMBER, 30, 0, 0, 0)], + displayOnCalendar = False, + ) + } + + language = simbase.config.GetString('language', 'english') + if language == 'english': + holidaysCommon.update(holidaysEnglish) + elif language == 'japanese': + holidaysCommon.update(holidaysJapanese) + elif language == 'german': + holidaysCommon.update(holidaysGerman) + elif language == 'french': + holidaysCommon.update(holidaysFrench) + else: + holidaysCommon.update(holidaysEnglish) + holidays = holidaysCommon + + if not language in ['japanese', 'german', 'portuguese', 'french'] : + if simbase.wantBingo: + holidays[ToontownGlobals.FISH_BINGO_NIGHT] = HolidayInfo_Weekly( + BingoNightHolidayAI.BingoNightHolidayAI, + # Fish Bingo Night - runs once a week + # Time: 12:00:01 am PST to 11:59:59 pm PST on Wednesdays + [(Day.WEDNESDAY, 0, 0, 1), + (Day.WEDNESDAY, 23, 59, 59), + ], + displayOnCalendar = True, + ) + + if simbase.wantKarts: + holidays[ToontownGlobals.KART_RECORD_DAILY_RESET] = HolidayInfo_Daily( + RaceManagerAI.KartRecordDailyResetter, + [(0, 24, 1), + (0, 24, 30), + ], + displayOnCalendar = False, + ) + holidays[ToontownGlobals.KART_RECORD_WEEKLY_RESET] = HolidayInfo_Weekly( + RaceManagerAI.KartRecordWeeklyResetter, + [(Day.MONDAY, 0, 25, 1), + (Day.MONDAY, 0, 25, 30), + ], + displayOnCalendar = False, + ) + holidays[ToontownGlobals.CIRCUIT_RACING] = HolidayInfo_Weekly( + RaceManagerAI.CircuitRaceHolidayMgr, + [(Day.MONDAY, 0, 0, 1), + (Day.MONDAY, 23, 59, 59), + ], + displayOnCalendar = True, + ) + holidays[ToontownGlobals.CIRCUIT_RACING_EVENT] = HolidayInfo_Yearly( + RaceManagerAI.CircuitRaceHolidayMgr, + [(Month.MAY, 21, 0, 0, 1), + (Month.MAY, 24, 23, 59, 59), + ], + displayOnCalendar = True, + ) + + if simbase.config.GetBool('want-trolley-holiday', 1): + holidays[ToontownGlobals.TROLLEY_HOLIDAY] = HolidayInfo_Weekly( + TrolleyHolidayMgrAI.TrolleyHolidayMgrAI, + [(Day.THURSDAY, 0, 0, 1), + (Day.THURSDAY, 23, 59, 59), + ], + displayOnCalendar = True, + ) + + if simbase.config.GetBool('want-trolley-holiday-everyday', 0): + holidays[ToontownGlobals.TROLLEY_HOLIDAY] = HolidayInfo_Daily( + TrolleyHolidayMgrAI.TrolleyHolidayMgrAI, + [(12, 0, 1), + (17, 59, 59), + ], + displayOnCalendar = False, + ) + + # Silly Saturday is a compound holiday - it is composed of alternating 2-hour blocks + # of Fish Bingo, Circuit Racing, and Trolley Holiday for 24 hours + + if simbase.config.GetBool('want-silly-saturday', 1): + holidays[ToontownGlobals.SILLY_SATURDAY_BINGO] = HolidayInfo_Weekly( + BingoNightHolidayAI.BingoNightHolidayAI, + [(Day.SATURDAY, 0, 0, 1), + (Day.SATURDAY, 1, 59, 59), + + (Day.SATURDAY, 6, 0, 0), + (Day.SATURDAY, 7, 59, 59), + + (Day.SATURDAY, 12, 0, 0), + (Day.SATURDAY, 13, 59, 59), + + (Day.SATURDAY, 18, 0, 0), + (Day.SATURDAY, 19, 59, 59), + ], + displayOnCalendar = True, + ) + holidays[ToontownGlobals.SILLY_SATURDAY_CIRCUIT] = HolidayInfo_Weekly( + RaceManagerAI.CircuitRaceHolidayMgr, + [(Day.SATURDAY, 2, 0, 0), + (Day.SATURDAY, 3, 59, 59), + + (Day.SATURDAY, 8, 0, 0), + (Day.SATURDAY, 9, 59, 59), + + (Day.SATURDAY, 14, 0, 0), + (Day.SATURDAY, 15, 59, 59), + + (Day.SATURDAY, 20, 0, 0), + (Day.SATURDAY, 21, 59, 59), + ], + displayOnCalendar = False, + ) + holidays[ToontownGlobals.SILLY_SATURDAY_TROLLEY] = HolidayInfo_Weekly( + TrolleyHolidayMgrAI.TrolleyHolidayMgrAI, + [(Day.SATURDAY, 4, 0, 0), + (Day.SATURDAY, 5, 59, 59), + + (Day.SATURDAY, 10, 0, 0), + (Day.SATURDAY, 11, 59, 59), + + (Day.SATURDAY, 16, 0, 0), + (Day.SATURDAY, 17, 59, 59), + + (Day.SATURDAY, 22, 0, 0), + (Day.SATURDAY, 23, 59, 59), + ], + displayOnCalendar = False, + ) + + holidays[ToontownGlobals.ROAMING_TRIALER_WEEKEND] = HolidayInfo_Oncely( + RoamingTrialerWeekendMgrAI.RoamingTrialerWeekendMgrAI, + [(2007,Month.DECEMBER, 3, 0, 0, 1), + (2007,Month.DECEMBER, 9, 23, 59, 59), + ], + displayOnCalendar = False, + ) + def __init__(self, air): self.air = air + # Dictionary of holidays in progress + # Maps holidayId: holidayObj self.currentHolidays = {} + self.createHolidays() + self.parseCalendarHolidays() - def isHolidayRunning(self, holidayId): - return holidayId in self.currentHolidays + def createHolidays(self): + currentTime = time.time() + localTime = time.localtime() + date = (localTime[0], # Current Year + localTime[1], # Current Month + localTime[2], # Current Day + localTime[6]) # Current WDay + for holidayId, holidayInfo in list(self.holidays.items()): + startTime = holidayInfo.getStartTime(date) + endTime = holidayInfo.getEndTime(date) + + self.notify.debug("holidayId = %s" % holidayId) + self.notify.debug("startTime = %s" % startTime) + self.notify.debug("endTime = %s" % endTime) + + try: + # See if we need to wrap the endTime around to next year + # For instance, a holiday that starts in December and ends + # in January would use this + if endTime < startTime: + end = time.localtime(endTime) + start = time.localtime(startTime) + + if end[2] == start[2]: + raise ValueError("createEvents: Invalid Start/End Tuple combination in holiday %s" %(holidayId)) + + newDate = holidayInfo.adjustDate(date) + endTime = holidayInfo.getEndTime(newDate) + self.notify.debug("wrapped: endTime = %s" % endTime) + + # Has the holiday not come yet? + if currentTime < startTime: + self.waitForHolidayStart(holidayId, startTime) + # Or, are we in the holiday now? + elif (currentTime >= startTime) and (currentTime < endTime): + self.startHoliday(holidayId) + # If the holiday already passed this year, + # wait for next years holiday + elif (currentTime >= startTime) and (currentTime >= endTime): + sTime = holidayInfo.getNextHolidayTime(currentTime) + + self.notify.debug("next: sTime = %s" % sTime) + # make sure it is not a one-time only event + if sTime != None: + if (currentTime >= sTime): + self.startHoliday(holidayId) + else: + self.waitForHolidayStart(holidayId, sTime) + else: + self.notify.info("One time holiday %s has passed" % holidayId) + + except ValueError as error: + self.notify.warning(str(error)) + + def waitForHolidayStart(self, holidayId, startTime): + currentTime = time.time() + waitTime = startTime - currentTime + taskName = "waitHoliday-start-" + str(holidayId) + task = taskMgr.doMethodLater(waitTime, self.startHolidayDoLater, taskName) + task.holidayId = holidayId + self.notify.info("Waiting until %s (- %s = %s) for holiday %s start" % + (time.ctime(startTime), time.ctime(currentTime), waitTime, holidayId)) + + def waitForHolidayEnd(self, holidayId, endTime): + self.notify.info("Waiting until %s for holiday %s end" % + (time.ctime(endTime), holidayId)) + waitTime = endTime - time.time() + taskName = "waitHoliday-end-" + str(holidayId) + task = taskMgr.doMethodLater(waitTime, self.endHolidayDoLater, taskName) + task.holidayId = holidayId + + def startHolidayDoLater(self, task): + self.startHoliday(task.holidayId) + return Task.done + + def nullifyDates(self, dates): + """This is a hacky way to get holidays not to intefere with the repeater + Needs to be changed by 2015""" + newDateTimes = [] + for item in dates: + if isinstance(item, datetime.datetime): + year = item.year+15 + if year>2030: + year = 2030 + newDateTimes.append(datetime.datetime(year, item.month, item.day, item.hour, item.second)) + else: + # These are start and end tuples + newDates = [] + for i in item: + year = i[0]+15 + if year>2030: + year = 2030 + newDates.append((year, i[1], i[2], i[3], i[4], i[5])) + return [(newDates[0], newDates[1])] + return newDateTimes + + def startHoliday(self, holidayId, testMode = 0): + self.notify.info("startHoliday: %s" % holidayId) + self.air.writeServerEvent('holiday', holidayId, 'start') + # Create the holiday object + holidayInfo = self.holidays[holidayId] + holidayClass = holidayInfo.getClass() + if holidayClass: + if holidayInfo.hasPhaseDates(): + if testMode == 0: + holidayObj = holidayClass(self.air, holidayId, + holidayInfo.tupleList, + holidayInfo.getPhaseDates()) + else: + startAndEndDates = self.nullifyDates(holidayInfo.tupleList) + phaseDates = self.nullifyDates(holidayInfo.getPhaseDates()) + holidayObj = holidayClass(self.air, holidayId, + startAndEndDates, + phaseDates) + elif hasattr(holidayInfo, 'isTestHoliday') and holidayInfo.isTestHoliday(): + testHolidays = holidayInfo.getTestHolidays() + holidayObj = holidayClass(self.air, holidayId, holidayInfo.tupleList, testHolidays) + else: + holidayObj = holidayClass(self.air, holidayId) + try: + # Start the holiday + holidayObj.start() + except SingletonError as error: + self.notify.warning("startHoliday: " + str(error)) + del holidayObj + return + # Store the current holiday for later reference + self.currentHolidays[holidayId] = holidayObj + else: + # Just store None, at least it will still indicate + # that a holiday was started + self.currentHolidays[holidayId] = None + + # Update the news manager, which in turn updates all the clients + self.updateNewsManager(list(self.currentHolidays.keys())) + + # Spawn a do later for the end of the holiday + currentTime = time.time() + localTime = time.localtime() + date = (localTime[0], # Current Year + localTime[1], # Current Month + localTime[2], # Current Day + localTime[6]) # Current WDay + + endTime = holidayInfo.getEndTime(date) + # Handle the case that the start and end times straddle the new year + if endTime < currentTime: + # Go to next year/month/week + date = holidayInfo.adjustDate(date) + endTime = holidayInfo.getEndTime(date) + self.waitForHolidayEnd(holidayId, endTime) + + def forcePhase(self, holidayId, newPhase): + """Force a phased holidy to go to a new phase. Returns True if succesful""" + result = False + holidayObj = self.currentHolidays.get(holidayId) + if holidayObj: + if hasattr(holidayObj, 'forcePhase'): + result = holidayObj.forcePhase(newPhase) + else: + self.notify.warning("%s does not have forcePhase" % holidayObj) + return result + + def endHolidayDoLater(self, task): + self.endHoliday(task.holidayId) + return Task.done + + def endHoliday(self, holidayId, stopForever = False): + self.notify.info("endHoliday: %s" % holidayId) + self.air.writeServerEvent('holiday', holidayId, 'end') + holidayInfo = self.holidays[holidayId] + + if holidayId in self.currentHolidays: + # Note - if the holiday does not define a class, + # the None object will be stored here + holidayObj = self.currentHolidays[holidayId] + if hasattr(holidayObj, 'goingToStop'): + holidayObj.goingToStop(stopForever) + return + else: + if holidayObj: + holidayObj.stop() + del self.currentHolidays[holidayId] + else: + self.notify.warning("Tried to stop a holiday that was not started") + + # Update the news manager, which in turn updates all the clients + # Send the negative of the holiday ID signifying the end of the holiday + self.updateNewsManager(list(self.currentHolidays.keys())) + + # Start the same holiday for the next time + currentTime = time.time() + startTime = holidayInfo.getNextHolidayTime(currentTime) + + self.notify.debug("currentTime = %s" % currentTime) + self.notify.debug("startTime = %s" % startTime) + + if isinstance (holidayInfo, HolidayInfo_Daily): + localTime = time.localtime() + date = (localTime[0], # Current Year + localTime[1], # Current Month + localTime[2], # Current Day + localTime[6]) # Current WDay + startTimeForToday = holidayInfo.getStartTime(date) + endTimeForToday = holidayInfo.getEndTime(date) + if (startTimeForToday < currentTime) and (currentTime < endTimeForToday): + #the task manager can sometimes end the task some seconds before we expected it + #avoid the case where we start it again for a few seconds then end it + oldStartTime = startTime + startTime = holidayInfo.getNextHolidayTime(endTimeForToday) + self.notify.debug("oldStart = %s newStart=%s" %(time.ctime(oldStartTime), time.ctime(startTime))) + + # if we are stopping the holiday prematurely, kill the task that waits for it to end + taskName = "waitHoliday-end-" + str(holidayId) + taskMgr.remove(taskName) + + # make sure it is not a one-time only event + if startTime != None: + # Handle the case that the start and end times straddle the new year + if startTime < currentTime: + # Go to next year + if not stopForever: + self.startHoliday(holidayId) + else: + if not stopForever: + self.waitForHolidayStart(holidayId, startTime) + + + + ####################################################################### + # This function is required for those holidays that required some + # time for cleanup. + ####################################################################### + + def delayedEnd(self, holidayId, stopForever = False): + self.notify.info("delayedEnd: %s" % holidayId) + holidayInfo = self.holidays[holidayId] + + if holidayId in self.currentHolidays: + # Note - if the holiday does not define a class, + # the None object will be stored here + holidayObj = self.currentHolidays[holidayId] + if holidayObj: + holidayObj.stop() + del self.currentHolidays[holidayId] + else: + self.notify.warning("Tried to stop a holiday that was not started") + + # Update the news manager, which in turn updates all the clients + # Send the negative of the holiday ID signifying the end of the holiday + self.updateNewsManager(list(self.currentHolidays.keys())) + + # Start the same holiday for the next time + currentTime = time.time() + startTime = holidayInfo.getNextHolidayTime(currentTime) + + self.notify.debug("currentTime = %s" % currentTime) + self.notify.debug("startTime = %s" % startTime) + + if isinstance (holidayInfo, HolidayInfo_Daily): + localTime = time.localtime() + date = (localTime[0], # Current Year + localTime[1], # Current Month + localTime[2], # Current Day + localTime[6]) # Current WDay + startTimeForToday = holidayInfo.getStartTime(date) + endTimeForToday = holidayInfo.getEndTime(date) + if (startTimeForToday < currentTime) and (currentTime < endTimeForToday): + #the task manager can sometimes end the task some seconds before we expected it + #avoid the case where we start it again for a few seconds then end it + oldStartTime = startTime + startTime = holidayInfo.getNextHolidayTime(endTimeForToday) + self.notify.debug("oldStart = %s newStart=%s" %(time.ctime(oldStartTime), time.ctime(startTime))) + + # make sure it is not a one-time only event + if startTime != None: + # Handle the case that the start and end times straddle the new year + if startTime < currentTime: + # Go to next year + if not stopForever: + self.startHoliday(holidayId) + else: + if not stopForever: + self.waitForHolidayStart(holidayId, startTime) + + def updateNewsManager(self, holidayIdList): + self.air.newsManager.d_setHolidayIdList(holidayIdList) def isMoreXpHolidayRunning(self): - return ToontownGlobals.MORE_XP_HOLIDAY in self.currentHolidays + """Return True if the double XP holiday is running.""" + keysList = list(self.currentHolidays.keys()) + result = False + if ToontownGlobals.MORE_XP_HOLIDAY in keysList: + result = True + return result + + def isHolidayRunning(self, holidayId): + """Return true if the indicated holidayId is running.""" + keysList = list(self.currentHolidays.keys()) + result = False + if holidayId in keysList: + result = True + return result + + def getCurPhase(self, holidayId): + """Return the current phase of the holiday, may return -1 if it doesn't know about phases.""" + result = -1 + if holidayId in self.currentHolidays: + holidayObj = self.currentHolidays[holidayId] + if holidayObj and hasattr(holidayObj,"getCurPhase"): + result = holidayObj.getCurPhase() + return result + + def parseCalendarHolidays(self): + """Tell the client of the toontown holidays displayed in the calendar.""" + for key in self.holidays: + holidayInfo = self.holidays[key] + if holidayInfo.displayOnCalendar: + if isinstance (holidayInfo, HolidayInfo_Weekly): + self.air.newsManager.addWeeklyCalendarHoliday(key, holidayInfo.tupleList[0][0][0]) + elif isinstance (holidayInfo, HolidayInfo_Yearly): + # we can have multiple start times and end times, just pick the bookends + firstStartTime = holidayInfo.tupleList[0][0] + lastEndTime = holidayInfo.tupleList[-1][-1] + self.air.newsManager.addYearlyCalendarHoliday(key, firstStartTime, lastEndTime) + elif isinstance (holidayInfo, HolidayInfo_Oncely): + if key in OncelyMultipleStartHolidays: + startAndEndList = [] + for times in holidayInfo.tupleList: + startAndEndList.append( (times[0], times[1])) + self.air.newsManager.addMultipleStartHoliday(key, startAndEndList) + else: + # we can have multiple start times and end times, just pick the bookends + firstStartTime = holidayInfo.tupleList[0][0] + lastEndTime = holidayInfo.tupleList[-1][-1] + self.air.newsManager.addOncelyCalendarHoliday(key, firstStartTime, lastEndTime) + elif isinstance (holidayInfo, HolidayInfo_Relatively): + # we can have multiple start times and end times, just pick the bookends + firstStartTime = holidayInfo.tupleList[0][0] + lastEndTime = holidayInfo.tupleList[-1][-1] + self.air.newsManager.addRelativelyCalendarHoliday(key, firstStartTime, lastEndTime) + + self.air.newsManager.sendWeeklyCalendarHolidays() + self.air.newsManager.sendYearlyCalendarHolidays() + self.air.newsManager.sendOncelyCalendarHolidays() + self.air.newsManager.sendMultipleStartHolidays() diff --git a/toontown/ai/HolidayRepeaterAI.py b/toontown/ai/HolidayRepeaterAI.py new file mode 100644 index 0000000..decdb84 --- /dev/null +++ b/toontown/ai/HolidayRepeaterAI.py @@ -0,0 +1,92 @@ +""" +The holidayRepeaterAI class repeats an existing holiday +over an infinite period of time +""" + +from toontown.toonbase import ToontownGlobals +from direct.directnotify import DirectNotifyGlobal +from . import HolidayBaseAI +from toontown.spellbook import ToontownMagicWordManagerAI + +scaleFactor = 12 +restartWaitTime = 60 +aiInitTime = 2.5 + +class HolidayRepeaterAI(HolidayBaseAI.HolidayBaseAI): + notify = DirectNotifyGlobal.directNotify.newCategory('HolidayRepeaterAI') + + def __init__(self, air, holidayId, startAndEndTuple, testHolidays): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + self.testHolidays = testHolidays + + self.testHolidayStates = {} + + self.aiInitialized = 0 + + def start(self): + """ + Immediately start running the test holidays on a loop + """ + if not hasattr(self.air, "holidayManager"): + taskMgr.doMethodLater(restartWaitTime, self.startLoop, "WaitForAir") + self.notify.warning("holidayManager not yet created") + return + elif self.aiInitialized == 0: + taskMgr.doMethodLater(aiInitTime, self.startLoop, "WaitForAir") + self.aiInitialized = 1 + + for holiday in list(self.testHolidays.keys()): + # Set the holiday state to show that it has not yet begun + if self.air.holidayManager.isHolidayRunning(holiday): + self.air.holidayManager.endHoliday(holiday, True) + self.testHolidayStates[holiday] = -1 + nextStepIn = self.testHolidays[holiday][0] + taskMgr.doMethodLater(nextStepIn*scaleFactor, self.handleNewHolidayState, "testHoliday_" + str(holiday), extraArgs=[holiday]) + + def startLoop(self, task): + self.start() + return task.done + + def handleNewHolidayState(self, holiday): + nextStepIn = -1 + self.testHolidayStates[holiday] = self.testHolidayStates[holiday] + 1 + curState = self.testHolidayStates[holiday] + if curState == 0: + if self.air.holidayManager.isHolidayRunning(holiday): + self.air.holidayManager.endHoliday(holiday, True) + self.notify.debug("Starting holiday: %s" %holiday) + simbase.air.newsManager.sendSystemMessage("Holiday " + str(holiday) + " started") + nextStepIn = self.testHolidays[holiday][(curState+1)] - self.testHolidays[holiday][curState] + self.air.holidayManager.startHoliday(holiday, testMode = 1) + elif len(self.testHolidays[holiday]) == (curState+1): + self.notify.debug("Ending holiday: %s" %holiday) + simbase.air.newsManager.sendSystemMessage("Holiday " + str(holiday) + " ended") + self.air.holidayManager.endHoliday(holiday, True) + self.testHolidayStates[holiday] = -1 + else: + self.notify.debug("Forcing holiday: %s to phase: %s" %(holiday, curState)) + simbase.air.newsManager.sendSystemMessage("Holiday " + str(holiday) + " is in phase "+str(curState)) + nextStepIn = self.testHolidays[holiday][(curState+1)] - self.testHolidays[holiday][curState] + self.air.holidayManager.forcePhase(holiday, curState) + if nextStepIn == -1: + count = 0 + for holiday in list(self.testHolidayStates.keys()): + if self.testHolidayStates[holiday] == -1: + count = count+1 + if count == len(self.testHolidays): + self.notify.debug("Finished hoiday cycle") + simbase.air.newsManager.sendSystemMessage("Holiday cycle complete: "+ str(self.holidayId)) + taskMgr.doMethodLater(restartWaitTime, self.startLoop, "RepeatHolidays") + else: + taskMgr.doMethodLater(nextStepIn*scaleFactor, self.handleNewHolidayState, "testHoliday_" + str(holiday), extraArgs=[holiday]) + + def stop(self): + """ + End all the Test holidays + """ + for holiday in list(self.testHolidays.keys()): + if taskMgr.hasTaskNamed("testHoliday_" + str(holiday)): + taskMgr.remove("testHoliday_" + str(holiday)) + self.air.holidayManager.endHoliday(holiday, True) + \ No newline at end of file diff --git a/toontown/ai/HydrantBuffHolidayAI.py b/toontown/ai/HydrantBuffHolidayAI.py new file mode 100644 index 0000000..5029e79 --- /dev/null +++ b/toontown/ai/HydrantBuffHolidayAI.py @@ -0,0 +1,16 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PropBuffHolidayAI +from toontown.ai import DistributedPhaseEventMgrAI +from toontown.toonbase import ToontownGlobals + +class HydrantBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'HydrantBuffHolidayAI') + + PostName = 'HydrantBuffHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + diff --git a/toontown/ai/HydrantZeroHolidayAI.py b/toontown/ai/HydrantZeroHolidayAI.py new file mode 100644 index 0000000..5059ea3 --- /dev/null +++ b/toontown/ai/HydrantZeroHolidayAI.py @@ -0,0 +1,47 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PhasedHolidayAI +from toontown.ai import DistributedHydrantZeroMgrAI +from toontown.toonbase import ToontownGlobals + +class HydrantZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'HydrantZeroHolidayAI') + + PostName = 'hydrantZeroHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + + def start(self): + # instantiate the object + PhasedHolidayAI.PhasedHolidayAI.start(self) + self.hydrantZeroMgr = DistributedHydrantZeroMgrAI.DistributedHydrantZeroMgrAI ( + self.air, self.startAndEndTimes, self.phaseDates) + self.hydrantZeroMgr.generateWithRequired(ToontownGlobals.UberZone) + # let the holiday system know we started + bboard.post(self.PostName) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(self.PostName) + # remove the object + #self.resistanceEmoteMgr.requestDelete() + self.hydrantZeroMgr.requestDelete() + + def forcePhase(self, newPhase): + """Force our holiday to a certain phase. Returns true if succesful""" + result = False + try: + newPhase = int(newPhase) + except: + newPhase = 0 + if newPhase >= self.hydrantZeroMgr.getNumPhases(): + self.notify.warning("newPhase %d invalid in forcePhase" % newPhase) + return + self.curPhase = newPhase + self.hydrantZeroMgr.forcePhase(newPhase) + result = True + return result + diff --git a/toontown/ai/MailboxBuffHolidayAI.py b/toontown/ai/MailboxBuffHolidayAI.py new file mode 100644 index 0000000..014aa9e --- /dev/null +++ b/toontown/ai/MailboxBuffHolidayAI.py @@ -0,0 +1,16 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PropBuffHolidayAI +from toontown.ai import DistributedPhaseEventMgrAI +from toontown.toonbase import ToontownGlobals + +class MailboxBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'MailboxBuffHolidayAI') + + PostName = 'MailboxBuffHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + diff --git a/toontown/ai/MailboxZeroHolidayAI.py b/toontown/ai/MailboxZeroHolidayAI.py new file mode 100644 index 0000000..2269776 --- /dev/null +++ b/toontown/ai/MailboxZeroHolidayAI.py @@ -0,0 +1,46 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PhasedHolidayAI +from toontown.ai import DistributedMailboxZeroMgrAI +from toontown.toonbase import ToontownGlobals + +class MailboxZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'MailboxZeroHolidayAI') + + PostName = 'mailboxZeroHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + + def start(self): + # instantiate the object + PhasedHolidayAI.PhasedHolidayAI.start(self) + self.mailboxZeroMgr = DistributedMailboxZeroMgrAI.DistributedMailboxZeroMgrAI ( + self.air, self.startAndEndTimes, self.phaseDates) + self.mailboxZeroMgr.generateWithRequired(ToontownGlobals.UberZone) + # let the holiday system know we started + bboard.post(self.PostName) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(self.PostName) + # remove the object + #self.resistanceEmoteMgr.requestDelete() + self.mailboxZeroMgr.requestDelete() + + def forcePhase(self, newPhase): + """Force our holiday to a certain phase. Returns true if succesful""" + result = False + try: + newPhase = int(newPhase) + except: + newPhase = 0 + if newPhase >= self.mailboxZeroMgr.getNumPhases(): + self.notify.warning("newPhase %d invalid in forcePhase" % newPhase) + return + self.curPhase = newPhase + self.mailboxZeroMgr.forcePhase(newPhase) + result = True + return result diff --git a/toontown/ai/NewsManagerAI.py b/toontown/ai/NewsManagerAI.py index 67e5d56..3e5ecbc 100644 --- a/toontown/ai/NewsManagerAI.py +++ b/toontown/ai/NewsManagerAI.py @@ -1,20 +1,170 @@ +from otp.ai.AIBaseGlobal import * +from pandac.PandaModules import * +from direct.distributed import DistributedObjectAI from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from toontown.toonbase import ToontownGlobals -class NewsManagerAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('NewsManagerAI') +class NewsManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("NewsManagerAI") + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.everyoneChats = simbase.config.GetBool("everyone-chats", 0) + self.weeklyCalendarHolidays = [] + self.yearlyCalendarHolidays = [] + self.oncelyCalendarHolidays = [] + self.relativelyCalendarHolidays = [] + self.multipleStartHolidays = [] + + def generate(self): + DistributedObjectAI.DistributedObjectAI.generate(self) + self.accept("avatarEntered", self.__handleAvatarEntered) + self.accept("avatarExited", self.__handleAvatarExited) + + def __handleAvatarEntered(self, avatar): + if self.air.suitInvasionManager.getInvading(): + # Let this poor avatar who just came in the game know that there + # is a Cog Invasion taking place + cogType, skeleton = self.air.suitInvasionManager.getCogType() + numRemaining = self.air.suitInvasionManager.getNumCogsRemaining() + self.sendAvatarInvasionStatus(avatar.getDoId(), cogType, numRemaining, skeleton) + + + # let them know about all holidays actually... + self.sendUpdateToAvatarId(avatar.getDoId(), "holidayNotify", []) + + if self.everyoneChats: + avatar.d_setCommonChatFlags(ToontownGlobals.CommonChat) + + def __handleAvatarExited(self, avatar = None): + pass + + def invasionBegin(self, cogType, numRemaining, skeleton): + self.sendUpdate("setInvasionStatus", + [ToontownGlobals.SuitInvasionBegin, cogType, numRemaining, skeleton]) + + def invasionEnd(self, cogType, numRemaining, skeleton): + self.sendUpdate("setInvasionStatus", + [ToontownGlobals.SuitInvasionEnd, cogType, numRemaining, skeleton]) + + def invasionUpdate(self, cogType, numRemaining, skeleton): + # Broadcast an invasion update to all players + self.sendUpdate("setInvasionStatus", + [ToontownGlobals.SuitInvasionUpdate, cogType, numRemaining, skeleton]) + + def sendAvatarInvasionStatus(self, avId, cogType, numRemaining, skeleton): + # Send an invasion update to only one avatar + self.sendUpdateToAvatarId(avId, "setInvasionStatus", + [ToontownGlobals.SuitInvasionBulletin, cogType, numRemaining, skeleton]) + + def sendSystemMessage(self, message, style = 0): + # Use news manager to broadcast a system message to all the clients + self.sendUpdate("sendSystemMessage", [message, style]) + + def d_setHolidayIdList(self, holidayIdList): + self.sendUpdate("setHolidayIdList", [holidayIdList]) + + def bingoWin(self, zoneId): + self.sendUpdate("setBingoWin", [0]) + + def bingoStart(self): + self.sendUpdate("setBingoStart", []) + + def bingoEnd(self): + self.sendUpdate("setBingoEnd", []) + + def circuitRaceStart(self): + self.sendUpdate("setCircuitRaceStart", []) + + def circuitRaceEnd(self): + self.sendUpdate("setCircuitRaceEnd", []) + + def trolleyHolidayStart(self): + self.sendUpdate("setTrolleyHolidayStart", []) + + def trolleyHolidayEnd(self): + self.sendUpdate("setTrolleyHolidayEnd", []) + + def trolleyWeekendStart(self): + self.sendUpdate("setTrolleyWeekendStart", []) + + def trolleyWeekendEnd(self): + self.sendUpdate("setTrolleyWeekendEnd", []) + + def roamingTrialerWeekendStart(self): + self.sendUpdate("setRoamingTrialerWeekendStart", []) + + def roamingTrialerWeekendEnd(self): + self.sendUpdate("setRoamingTrialerWeekendEnd", []) + + def addWeeklyCalendarHoliday(self, holidayId, dayOfTheWeek): + """Add a new weekly holiday displayed in the calendar.""" + self.weeklyCalendarHolidays.append((holidayId, dayOfTheWeek)) def getWeeklyCalendarHolidays(self): - return [] + """Return our list of weekly calendar holidays.""" + return self.weeklyCalendarHolidays + + def sendWeeklyCalendarHolidays(self): + """Force a send of the weekly calendar holidays.""" + self.sendUpdate("setWeeklyCalendarHolidays", [self.weeklyCalendarHolidays]) + + def addYearlyCalendarHoliday(self, holidayId, firstStartTime, lastEndTime): + """Add a new yearly holiday.""" + # Note the holiday can have breaks in it. e.g. no bloodsucker invasion + # happens between 3 and 6 pm on halloween, however for simplicity + # we just note the first time it will happen, and the last end time for it + self.yearlyCalendarHolidays.append((holidayId, firstStartTime, lastEndTime)) def getYearlyCalendarHolidays(self): - return [] + """Return our list of yearly calendar holidays.""" + return self.yearlyCalendarHolidays + + def sendYearlyCalendarHolidays(self): + """Force a send of the yearly calendar holidays.""" + self.sendUpdate("setYearlyCalendarHolidays", [self.yearlyCalendarHolidays]) + + def addOncelyCalendarHoliday(self, holidayId, firstStartTime, lastEndTime): + """Add a new oncely holiday.""" + # Note the holiday can have breaks in it. e.g. no bloodsucker invasion + # happens between 3 and 6 pm on halloween, however for simplicity + # we just note the first time it will happen, and the last end time for it + self.oncelyCalendarHolidays.append((holidayId, firstStartTime, lastEndTime)) def getOncelyCalendarHolidays(self): - return [] + """Return our list of oncely calendar holidays.""" + return self.oncelyCalendarHolidays - def getRelativelyCalendarHolidays(self): - return [] + def addMultipleStartHoliday(self, holidayId, startAndEndList): + """A a new multiple start holiday.""" + # For a oncely holiday where we want to use only one holiday id + # but it becomes useful to expose the multiple start times + self.multipleStartHolidays.append((holidayId, startAndEndList)) def getMultipleStartHolidays(self): - return [] + """Return our list of multiple start holidays.""" + return self.multipleStartHolidays + + def sendMultipleStartHolidays(self): + """Force a send of the oncely calendar holidays.""" + self.sendUpdate("setMultipleStartHolidays", [self.multipleStartHolidays]) + + def sendOncelyCalendarHolidays(self): + """Force a send of the oncely calendar holidays.""" + self.sendUpdate("setOncelyCalendarHolidays", [self.oncelyCalendarHolidays]) + + def addRelativelyCalendarHoliday(self, holidayId, firstStartTime, lastEndTime): + """Add a new oncely holiday.""" + # Note the holiday can have breaks in it. e.g. no bloodsucker invasion + # happens between 3 and 6 pm on halloween, however for simplicity + # we just note the first time it will happen, and the last end time for it + self.relativelyCalendarHolidays.append((holidayId, firstStartTime, lastEndTime)) + + def getRelativelyCalendarHolidays(self): + """Return our list of Relatively calendar holidays.""" + return self.relativelyCalendarHolidays + + def sendRelativelyCalendarHolidays(self): + """Force a send of the Relatively calendar holidays.""" + self.sendUpdate("setRelativelyCalendarHolidays", [self.relativelyCalendarHolidays]) + diff --git a/toontown/ai/PhasedHolidayAI.py b/toontown/ai/PhasedHolidayAI.py new file mode 100644 index 0000000..825cfb5 --- /dev/null +++ b/toontown/ai/PhasedHolidayAI.py @@ -0,0 +1,140 @@ +import datetime +import time +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.toonbase import ToontownGlobals +from toontown.ai import DistributedResistanceEmoteMgrAI + + +class StartAndEndTime: + def __init__(self, startTime, endTime): + """Keep track of start and end datetimes, in a struct.""" + self.start = startTime + self.end = endTime + + def isInBetween(self, phaseTime): + """Returns true if phaseTime is in between start and end times. + Note that it returns false if it is equal.""" + result = False + if self.start < phaseTime and phaseTime < self.end: + result = True + return result + + def isInBetweenInclusive(self, phaseTime): + """Returns true if phaseTime is in between start and end times. + Note that it returns true if it is equal.""" + result = False + if self.start <= phaseTime and phaseTime <= self.end: + result = True + return True + + def __repr__(self): + return self.__str__() + + def __str__(self): + return ("(%s %s)" % (self.start, self.end)) + +class PhasedHolidayAI(HolidayBaseAI.HolidayBaseAI): + """This is the base class for holidays which have different phases. + + They ramp up over time, such as hydrant zero, silly meter. + While each phase could have been implemented as a different holiday, + this makes sure the start and end times for each phase match up.""" + + # WARNING phased holidays with multiple start and end times have not been fully tested + # RAU coded with it in mind, but edge cases can easily slip through + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'PhasedHolidayAI') + + def __init__(self, air, holidayId, startAndEndTupleList, phaseDates): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + self.oldPhase = 0 + self.curPhase = 0 + self.phaseDates = phaseDates + self.startAndEndTimes = self.convertStartAndEndListToDateTimes(startAndEndTupleList) + self.sanityCheckPhaseDates() + # we assume phase 0 is before phaseDates[0], + # phase 1 is between phaseDates[0], phaseDates[1] + # the last phase is after phaseDates[-1] + # TODO should we have the FSM here or in the child class? + # Lets leave it in the child class in case it's really simple + + def convertStartAndEndListToDateTimes(self, startAndEndTupleList): + """Convert start and end times to a more manageable class.""" + result = [] + for startAndEnd in startAndEndTupleList: + startInfo = startAndEnd[0] + startTime = datetime.datetime(startInfo[0], startInfo[1], startInfo[2], + startInfo[3], startInfo[4], startInfo[5]) + endInfo = startAndEnd[1] + endTime = datetime.datetime(endInfo[0], endInfo[1], endInfo[2], + endInfo[3], endInfo[4], endInfo[5]) + result.append(StartAndEndTime(startTime, endTime)) + return result + + + def sanityCheckPhaseDates(self): + """Do some sanity checking on our phase dates.""" + #Check phase dates are between end and start times.""" + for phaseDate in self.phaseDates: + foundInBetween = False + for startAndEnd in self.startAndEndTimes: + if startAndEnd.isInBetween(phaseDate): + foundInBetween = True + break + if not foundInBetween: + self.notify.error("holiday %d, phaseDate=%s not in between start and end times" % + (self.holidayId, phaseDate)) + # check the phase dates are ascending + for index in range( len(self.phaseDates) -1): + if not (self.phaseDates[index] < self.phaseDates[index +1]): + self.notify.error("phaseDate=%s coming before phaseDate=%s" % + (self.phaseDates[index], self.phaseDates[index+1])) + + def calcPhase(self, myTime): + """Return which phase we should be given parameter time. + + Will return 0 if it's way before the start time, + and return the last phase if its way after the end time + """ + result = self.getNumPhases() + for index, phaseDate in enumerate( self.phaseDates): + if myTime < phaseDate: + result = index + break + return result + + def getNumPhases(self): + """Return how many phases we have.""" + result = len(self.phaseDates) + 1 + return result + + def isValidStart(self, curStartTime): + """Print out a message if we're starting at a valid time. + + We could start early or later if it's forced by magic words.""" + result = False + for startAndEnd in self.startAndEndTimes: + if startAndEnd.isInBetweenInclusive(curStartTime): + result = True + break + + def start(self): + """Start the holiday and set us to the correct phase.""" + # start the holiday + # this equivalent to the same bit of code we use in HolidayManagerAI.createHolidays + curTime = datetime.datetime.today() + isValidStart = self.isValidStart(curTime) + if not isValidStart: + self.notify.warning("starting holiday %d at %s but self.startAndEndTimes= %s " % + (self.holidayId, curTime, self.startAndEndTimes)) + + pass + + + def stop(self): + pass + + def forcePhase(self, newPhase): + self.notify.warning("Child class must defined forcePhase") diff --git a/toontown/ai/PolarPlaceEventMgrAI.py b/toontown/ai/PolarPlaceEventMgrAI.py new file mode 100644 index 0000000..83c3f2b --- /dev/null +++ b/toontown/ai/PolarPlaceEventMgrAI.py @@ -0,0 +1,31 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.toonbase import ToontownGlobals +from toontown.ai import DistributedPolarPlaceEffectMgrAI + +EVENT_ZONE = 3821 # 'Hibernation Vacations' interior + +class PolarPlaceEventMgrAI(HolidayBaseAI.HolidayBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'PolarPlaceEventMgrAI') + + PostName = 'polarPlaceEvent' + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + self.polarPlaceEmoteMgr = None + + def start(self): + # instantiate the object + self.polarPlaceEmoteMgr = DistributedPolarPlaceEffectMgrAI.DistributedPolarPlaceEffectMgrAI( + self.air) + self.polarPlaceEmoteMgr.generateWithRequired(EVENT_ZONE) + # let the holiday system know we started + bboard.post(PolarPlaceEventMgrAI.PostName) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(PolarPlaceEventMgrAI.PostName) + # remove the object + self.polarPlaceEmoteMgr.requestDelete() diff --git a/toontown/ai/PropBuffHolidayAI.py b/toontown/ai/PropBuffHolidayAI.py new file mode 100644 index 0000000..adf5a52 --- /dev/null +++ b/toontown/ai/PropBuffHolidayAI.py @@ -0,0 +1,54 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PhasedHolidayAI +from toontown.ai import DistributedPhaseEventMgrAI +from toontown.toonbase import ToontownGlobals + +class PropBuffHolidayAI(PhasedHolidayAI.PhasedHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'PropBuffHolidayAI') + + # PostName = 'propBuffHoliday' # deliberately not set so child classes forced to define this + # and we avoid a conflict of say the laff buf holiday stomping on the drop buff holiday + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + + def start(self): + # instantiate the object + PhasedHolidayAI.PhasedHolidayAI.start(self) + self.propBuffMgr = DistributedPhaseEventMgrAI.DistributedPhaseEventMgrAI ( + self.air, self.startAndEndTimes, self.phaseDates) + self.propBuffMgr.generateWithRequired(ToontownGlobals.UberZone) + # let the holiday system know we started + bboard.post(self.PostName) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(self.PostName) + # remove the object + #self.resistanceEmoteMgr.requestDelete() + self.propBuffMgr.requestDelete() + + def forcePhase(self, newPhase): + """Force our holiday to a certain phase. Returns true if succesful""" + result = False + try: + newPhase = int(newPhase) + except: + newPhase = 0 + if newPhase >= self.propBuffMgr.getNumPhases(): + self.notify.warning("newPhase %d invalid in forcePhase" % newPhase) + return + self.curPhase = newPhase + self.propBuffMgr.forcePhase(newPhase) + result = True + return result + + def getCurPhase(self): + """Returns the buffMgr's current phase, may return -1.""" + result = -1 + if hasattr(self,"propBuffMgr"): + result = self.propBuffMgr.getCurPhase() + return result diff --git a/toontown/ai/ResistanceEventMgrAI.py b/toontown/ai/ResistanceEventMgrAI.py new file mode 100644 index 0000000..8a7417f --- /dev/null +++ b/toontown/ai/ResistanceEventMgrAI.py @@ -0,0 +1,31 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.toonbase import ToontownGlobals +from toontown.ai import DistributedResistanceEmoteMgrAI + +EVENT_ZONE = 9720 # 'Talking in Your Sleep Voiceover Training' interior + +class ResistanceEventMgrAI(HolidayBaseAI.HolidayBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'ResistanceEventMgrAI') + + PostName = 'resistanceEvent' + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + self.resistanceEmoteMgr = None + + def start(self): + # instantiate the object + self.resistanceEmoteMgr = DistributedResistanceEmoteMgrAI.DistributedResistanceEmoteMgrAI( + self.air) + self.resistanceEmoteMgr.generateWithRequired(EVENT_ZONE) + # let the holiday system know we started + bboard.post(ResistanceEventMgrAI.PostName) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(ResistanceEventMgrAI.PostName) + # remove the object + self.resistanceEmoteMgr.requestDelete() diff --git a/toontown/ai/RoamingTrialerWeekendMgrAI.py b/toontown/ai/RoamingTrialerWeekendMgrAI.py new file mode 100644 index 0000000..9d768bf --- /dev/null +++ b/toontown/ai/RoamingTrialerWeekendMgrAI.py @@ -0,0 +1,32 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals, TTLocalizer +from toontown.ai import HolidayBaseAI + +class RoamingTrialerWeekendMgrAI(HolidayBaseAI.HolidayBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'RoamingTrialerWeekendMgrAI') + + PostName = 'RoamingTrialerWeekend' + StartStopMsg = 'RoamingTrialerWeekendStartStop' + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + def start(self): + # let the holiday system know we started + bboard.post(RoamingTrialerWeekendMgrAI.PostName, True) + + # tell everyone race night is starting + simbase.air.newsManager.roamingTrialerWeekendStart() + + messenger.send(RoamingTrialerWeekendMgrAI.StartStopMsg) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(RoamingTrialerWeekendMgrAI.PostName) + + # tell everyone race night is stopping + simbase.air.newsManager.roamingTrialerWeekendEnd() + + messenger.send(RoamingTrialerWeekendMgrAI.StartStopMsg) diff --git a/toontown/ai/ScavengerHuntMgrAI.py b/toontown/ai/ScavengerHuntMgrAI.py new file mode 100644 index 0000000..a068398 --- /dev/null +++ b/toontown/ai/ScavengerHuntMgrAI.py @@ -0,0 +1,216 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.interval.IntervalGlobal import * +from toontown.ai import HolidayBaseAI +from otp.otpbase import OTPGlobals +from toontown.toonbase import ToontownGlobals +from toontown.hood import ZoneUtil +from toontown.ai import DistributedScavengerHuntTargetAI +from toontown.scavengerhunt.ScavengerHuntBase import ScavengerHuntBase +from toontown.uberdog.DataStoreAIClient import DataStoreAIClient +from toontown.uberdog import DataStoreGlobals + +import time +import pickle + +# This dictionary defines the relationship between the scavenger hunt goal id and the zone where the goal is located. +# goalId: zoneId +GOALS = { + # 0 : 2649, # TTC + # 1 : 1834, # DD + # 2 : 4835, # MM + # 3 : 5620, # DG + # 4 : 3707, # BR + # 5 : 9619, # DL + 0 : 1000, + 1 : 2000, + } + +# This dictionary defines the milestones for this scavenger hunt +MILESTONES = { + #0: (GOALS.keys(), 'All Trick-or-Treat goals found'), + 0: ((0, 1), 'All Scavenger hunt goals found'), + } + +class ScavengerHuntMgrAI(HolidayBaseAI.HolidayBaseAI): + """ + This is the main Scavenger hunt holiday class. It will create + several distributed listeners in selected zones. These listeners + will wait for a toon to trigger something; for example an SC event. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'ScavengerHuntMgrAI') + + PostName = 'ScavangerHunt' + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + self.hunt = None + self.targets = {} + self.storeClient = DataStoreAIClient(air, + DataStoreGlobals.SH, + self.receiveScavengerHuntResults) + + @property + def goals(self): + return GOALS + + @property + def milestones(self): + return MILESTONES + + def start(self): + # Create a unique id for this hunt based on it's start date and time + localTime = time.localtime() + date = (localTime[0], + localTime[1], + localTime[2], + localTime[6], + ) + from toontown.ai import HolidayManagerAI + startTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getStartTime(date) + scavengerHuntId = abs(hash(time.ctime(startTime))) + + # Create the hunt + self.hunt = ScavengerHuntBase(scavengerHuntId, self.holidayId) + self.hunt.defineGoals(list(self.goals.keys())) + # Send a list with one item in it: [0, (0, 1, 2, 3, 4, 5)] + # This milestone defines the end of the hunt. + + self.hunt.defineMilestones((x[0],x[1][0]) for x in list(self.milestones.items())) + + self.createListeners() + + # let the holiday system know we started + bboard.post(ScavengerHuntMgrAI.PostName) + + # make sure the uberdog data store is up for this hunt + self.storeClient.openStore() + + def createListeners(self): + """ + Create the listeners that will look for an event in the relavent zone + """ + for id in list(self.goals.keys()): + mgrAI = DistributedScavengerHuntTargetAI.DistributedScavengerHuntTargetAI(self.air, + self.hunt, + id, + self, + ) + self.targets[id] = mgrAI + self.targets[id].generateWithRequired(self.goals[id]) + + + def stop(self): + # let the holiday system know we stopped + bboard.remove(ScavengerHuntMgrAI.PostName) + # remove the targetAI's and their distributed counterparts + for zone in list(self.goals.keys()): + self.targets[zone].requestDelete() + + self.storeClient.closeStore() + + def avatarAttemptingGoal(self, avId, goal): + # We need to know what goals have already been completed + # in order to proceed. Ask the Uberdog for the data. + # We send the goal since when the Uberdog responds we + # still need to match the new goal with the avId. + queryData = (avId, goal) + self.sendScavengerHuntQuery('GetGoals', queryData) + + def avatarCompletedGoal(self, avId, goal): + # This will ask the Uberdog to add this new goal + # to the avId's completed list. + queryData = (avId, goal) + self.sendScavengerHuntQuery('AddGoal', queryData) + + def sendScavengerHuntQuery(self, qTypeString, qData): + # send a finalized query to the Uberdog. + self.storeClient.sendQuery(qTypeString, qData) + + def receiveScavengerHuntResults(self, results): + # This function handles the result message from + # the Uberdog. See the ScavengerHuntDataStore + # class to see data format. + + # Indicates that the qId was invalid for this store. + # Should never really happen + if results == None: + return + else: + # extract the queryId, and translate it to it's string + qId, data = results + qType = self.storeClient.getQueryTypeString(qId) + + # We're receiving an avatar's new goal and list of completed goals + if qType == 'GetGoals': + avId, goal, done_goals = data + # See what needs to be done + self.__handleResult(avId, done_goals, goal) + # The goal was successfully added to this avId + elif qType == 'AddGoal': + avId, = data + + def __handleResult(self, avId, done_goals, goal): + # This is where we check to see if the goal has already been completed. + # If it's already done, let the client know. Otherwise, check to see + # if the scavenger hunt is complete and respond accordingly. + if goal in done_goals: + ScavengerHuntMgrAI.notify.debug( + repr(avId)+' already found Scavenger hunt target '+repr(goal)+': '+repr(self.goals.get(goal, 'INVALID GOAL ID IN '+self.PostName))) + av = self.air.doId2do.get(avId) + + milestoneIds = self.hunt.getRecentMilestonesHit(done_goals+[goal], goal) + + if milestoneIds: + for id in milestoneIds: + ScavengerHuntMgrAI.notify.debug( + repr(avId)+' hit milestone ' + repr(id) + ': ' + self.milestones.get(milestoneIds[id], [None, 'Undefined milestone id in '+self.PostName])[1]) + + if (id == 0): # handle found all targets + self.huntCompletedReward(avId, goal) + else: + self.huntGoalFound(avId, goal) + else: + self.huntGoalAlreadyFound(avId) + elif 0 <= goal <= len(list(self.goals.keys())): + ScavengerHuntMgrAI.notify.debug( + repr(avId)+' found Scavenger hunt target '+repr(goal)) + av = self.air.doId2do.get(avId) + + if not av: + ScavengerHuntMgrAI.notify.warning( + 'Tried to send goal feedback to av %s, but they left' % avId) + else: + milestoneIds = self.hunt.getRecentMilestonesHit(done_goals+[goal], goal) + + if milestoneIds: + for id in milestoneIds: + ScavengerHuntMgrAI.notify.debug( + repr(avId)+' hit milestone ' + repr(id) + ': ' + self.milestones.get(milestoneIds[id], [None, 'Undefined milestone id in '+self.PostName])[1]) + + if (id == 0): # handle found all targets + # Wait for the goal found reward to complete + taskMgr.doMethodLater(10, self.huntCompletedReward, repr(avId)+'-huntCompletedReward', extraArgs = [avId, goal, True]) + self.huntGoalFound(avId, goal) + else: + self.huntGoalFound(avId, goal) + + def huntCompletedReward(self, avId, goal, firstTime = False): + """ + Reward the Toon + """ + pass + + def huntGoalAlreadyFound(self, avId): + """ + This goal has already been found + """ + pass + + def huntGoalFound(self, avId, goal): + """ + One of the goals in the milestone were found, + so we reward the toon. + """ + pass \ No newline at end of file diff --git a/toontown/ai/SillyMeterHolidayAI.py b/toontown/ai/SillyMeterHolidayAI.py new file mode 100644 index 0000000..92f509a --- /dev/null +++ b/toontown/ai/SillyMeterHolidayAI.py @@ -0,0 +1,48 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PhasedHolidayAI +from toontown.ai import DistributedSillyMeterMgrAI +from toontown.toonbase import ToontownGlobals + +class SillyMeterHolidayAI(PhasedHolidayAI.PhasedHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'SillyMeterHolidayAI') + + PostName = 'SillyMeterHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + + self.runningState = 1 + + def start(self): + # instantiate the object + PhasedHolidayAI.PhasedHolidayAI.start(self) + self.SillyMeterMgr = DistributedSillyMeterMgrAI.DistributedSillyMeterMgrAI( + self.air, self.startAndEndTimes, self.phaseDates) + self.SillyMeterMgr.generateWithRequired(ToontownGlobals.UberZone) + # let the holiday system know we started + bboard.post(self.PostName) + + def stop(self): + # let the holiday system know we stopped + self.runningState = 0 + bboard.remove(self.PostName) + self.SillyMeterMgr.end() + self.SillyMeterMgr.requestDelete() + + def forcePhase(self, newPhase): + """Force our holiday to a certain phase.""" + try: + newPhase = int(newPhase) + except: + newPhase = 0 + if newPhase >= self.SillyMeterMgr.getNumPhases(): + self.notify.warning("newPhase %d invalid in forcePhase" % newPhase) + return + self.curPhase = newPhase + self.SillyMeterMgr.forcePhase(newPhase) + + def getRunningState(self): + return self.runningState diff --git a/toontown/ai/ToontownAIRepository.py b/toontown/ai/ToontownAIRepository.py index 19e549c..21771b0 100644 --- a/toontown/ai/ToontownAIRepository.py +++ b/toontown/ai/ToontownAIRepository.py @@ -117,14 +117,15 @@ class ToontownAIRepository(ToontownInternalRepository): # Setup necessary files and things. self.setupFiles() - + + # Create our global objects. + self.notify.info('Creating global objects...') + self.createGlobals() # Create our local objects. self.notify.info('Creating local objects...') self.createLocals() - # Create our global objects. - self.notify.info('Creating global objects...') - self.createGlobals() + # Create our zones. self.notify.info('Creating zones...') diff --git a/toontown/ai/TrashcanBuffHolidayAI.py b/toontown/ai/TrashcanBuffHolidayAI.py new file mode 100644 index 0000000..06e0ab6 --- /dev/null +++ b/toontown/ai/TrashcanBuffHolidayAI.py @@ -0,0 +1,16 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PropBuffHolidayAI +from toontown.ai import DistributedPhaseEventMgrAI +from toontown.toonbase import ToontownGlobals + +class TrashcanBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'TrashcanBuffHolidayAI') + + PostName = 'TrashcanBuffHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + diff --git a/toontown/ai/TrashcanZeroHolidayAI.py b/toontown/ai/TrashcanZeroHolidayAI.py new file mode 100644 index 0000000..84402e0 --- /dev/null +++ b/toontown/ai/TrashcanZeroHolidayAI.py @@ -0,0 +1,47 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from toontown.ai import PhasedHolidayAI +from toontown.ai import DistributedTrashcanZeroMgrAI +from toontown.toonbase import ToontownGlobals + +class TrashcanZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'TrashcanZeroHolidayAI') + + PostName = 'trashcanZeroHoliday' + + def __init__(self, air, holidayId, startAndEndTimes, phaseDates): + PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates) + + def start(self): + # instantiate the object + PhasedHolidayAI.PhasedHolidayAI.start(self) + self.trashcanZeroMgr = DistributedTrashcanZeroMgrAI.DistributedTrashcanZeroMgrAI ( + self.air, self.startAndEndTimes, self.phaseDates) + self.trashcanZeroMgr.generateWithRequired(ToontownGlobals.UberZone) + # let the holiday system know we started + bboard.post(self.PostName) + + def stop(self): + # let the holiday system know we stopped + bboard.remove(self.PostName) + # remove the object + #self.resistanceEmoteMgr.requestDelete() + self.trashcanZeroMgr.requestDelete() + + def forcePhase(self, newPhase): + """Force our holiday to a certain phase. Returns true if succesful""" + result = False + try: + newPhase = int(newPhase) + except: + newPhase = 0 + if newPhase >= self.trashcanZeroMgr.getNumPhases(): + self.notify.warning("newPhase %d invalid in forcePhase" % newPhase) + return + self.curPhase = newPhase + self.trashcanZeroMgr.forcePhase(newPhase) + result = True + return result + diff --git a/toontown/ai/TrickOrTreatMgrAI.py b/toontown/ai/TrickOrTreatMgrAI.py new file mode 100644 index 0000000..d079bdd --- /dev/null +++ b/toontown/ai/TrickOrTreatMgrAI.py @@ -0,0 +1,103 @@ +from . import ScavengerHuntMgrAI +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.ai import DistributedTrickOrTreatTargetAI +from otp.otpbase import OTPGlobals + +import time + +GOALS = { + 0 : 2649, # TTC + 1 : 1834, # DD + 2 : 4835, # MM + 3 : 5620, # DG + 4 : 3707, # BR + 5 : 9619, # DL + } + +# This dictionary defines the milestones for this scavenger hunt +MILESTONES = { + 0: ((0, 1, 2, 3, 4, 5), 'All Trick-or-Treat goals found'), + } + +class TrickOrTreatMgrAI(ScavengerHuntMgrAI.ScavengerHuntMgrAI): + """ + This is the TrickOrTreat manager that extends the scanvenger hunt + by providing unique rewards and milestones. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('TrickOrTreatMgrAI') + + def __init__(self, air, holidayId): + ScavengerHuntMgrAI.ScavengerHuntMgrAI.__init__(self, air, holidayId) + + def createListeners(self): + """ + Create the listeners that will look for an event in the relavent zone + """ + for id in list(self.goals.keys()): + mgrAI = DistributedTrickOrTreatTargetAI.DistributedTrickOrTreatTargetAI(self.air, + self.hunt, + id, + self, + ) + self.targets[id] = mgrAI + self.targets[id].generateWithRequired(self.goals[id]) + + @property + def goals(self): + return GOALS + + @property + def milestones(self): + return MILESTONES + + def huntCompletedReward(self, avId, goal, firstTime = False): + """ + Reward the Toon for completing the TrickOrTreat with + a pumpkin head + """ + if firstTime: + self.air.writeServerEvent('pumpkinHeadEarned', avId, 'Trick-or-Treat scavenger hunt complete.') + + av = self.air.doId2do.get(avId) + localTime = time.localtime() + date = (localTime[0], + localTime[1], + localTime[2], + localTime[6], + ) + + from toontown.ai import HolidayManagerAI + endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(date) + endTime += ToontownGlobals.TOT_REWARD_END_OFFSET_AMOUNT + + if not av: + self.notify.warning( + 'Tried to send milestone feedback to av %s, but they left' % avId) + else: + av.b_setCheesyEffect(OTPGlobals.CEPumpkin, 0, (time.time()/60)+1) + #av.b_setCheesyEffect(OTPGlobals.CEPumpkin, 0, endTime/60) + + def huntGoalAlreadyFound(self, avId): + """ + This goal has already been found + """ + av = self.air.doId2do.get(avId) + if not av: + self.notify.warning( + 'Tried to send goal feedback to av %s, but they left' % avId) + else: + av.sendUpdate('trickOrTreatTargetMet', [0]) + + def huntGoalFound(self, avId, goal): + """ + One of the goals in the milestone were found, + so we reward the toon. + """ + av = self.air.doId2do.get(avId) + # Do all the updates at once + av.addMoney(ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT) + self.avatarCompletedGoal(avId, goal) + # Start jellybean reward effect + av.sendUpdate('trickOrTreatTargetMet', [ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT]) \ No newline at end of file diff --git a/toontown/ai/ValentinesDayMgrAI.py b/toontown/ai/ValentinesDayMgrAI.py new file mode 100644 index 0000000..3eb5262 --- /dev/null +++ b/toontown/ai/ValentinesDayMgrAI.py @@ -0,0 +1,23 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals, TTLocalizer +from toontown.ai import HolidayBaseAI + +class ValentinesDayMgrAI(HolidayBaseAI.HolidayBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'ValentinesDayMgrAI') + + PostName = 'ValentinesDay' + StartStopMsg = 'ValentinesDayStartStop' + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + def start(self): + # Let the holiday system know we started + bboard.post(ValentinesDayMgrAI.PostName, True) + + def stop(self): + # Let the holiday system know we stopped + bboard.remove(ValentinesDayMgrAI.PostName) + \ No newline at end of file diff --git a/toontown/ai/WinterCarolingMgrAI.py b/toontown/ai/WinterCarolingMgrAI.py new file mode 100644 index 0000000..e30b309 --- /dev/null +++ b/toontown/ai/WinterCarolingMgrAI.py @@ -0,0 +1,112 @@ +from . import ScavengerHuntMgrAI +from direct.directnotify import DirectNotifyGlobal +from toontown.toonbase import ToontownGlobals +from toontown.ai import DistributedWinterCarolingTargetAI +from otp.otpbase import OTPGlobals + +import time + +GOALS = { + 0 : 2659, # Joy Buzzer to the world, Silly Street, Toontown Central + 1 : 1707, # Gifts With A Porpoise, Seaweed Street, Donalds Dock + 2 : 5626, # Pine Needle Crafts, Elm Street, Daisy's Garden + 3 : 4614, # Shave and Haircut for a song, Alto Avenue, Minnie's Melodyland + 4 : 3828, # Snowman's Land, Polar Place, The Brrrgh + 5 : 9720, # Talking in Your Sleep Voice Training, Pajama Place, Donald's Dreamland + } + +# This dictionary defines the milestones for this scavenger hunt +MILESTONES = { + 0: ((0, 1, 2, 3, 4, 5), 'All Winter Caroling goals found'), + } + +class WinterCarolingMgrAI(ScavengerHuntMgrAI.ScavengerHuntMgrAI): + """ + This is the WinterCaroling manager that extends the scanvenger hunt + by providing unique rewards and milestones. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('WinterCarolingMgrAI') + + def __init__(self, air, holidayId): + ScavengerHuntMgrAI.ScavengerHuntMgrAI.__init__(self, air, holidayId) + + def createListeners(self): + """ + Create the listeners that will look for an event in the relavent zone + """ + for id in list(self.goals.keys()): + mgrAI = DistributedWinterCarolingTargetAI.DistributedWinterCarolingTargetAI(self.air, + self.hunt, + id, + self, + ) + self.targets[id] = mgrAI + self.targets[id].generateWithRequired(self.goals[id]) + + @property + def goals(self): + return GOALS + + @property + def milestones(self): + return MILESTONES + + def huntCompletedReward(self, avId, goal, firstTime = False): + """ + Reward the Toon for completing the WinterCaroling with + a pumpkin head + """ + if firstTime: + self.air.writeServerEvent('pumpkinHeadEarned', avId, 'WinterCaroling scavenger hunt complete.') + + av = self.air.doId2do.get(avId) + localTime = time.localtime() + date = (localTime[0], + localTime[1], + localTime[2], + localTime[6], + ) + + from toontown.ai import HolidayManagerAI + endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(date) + startTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getStartTime(date) + + if endTime < startTime: + end = time.localtime(endTime) + start = time.localtime(startTime) + + newDate = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].adjustDate(date) + endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(newDate) + + endTime += ToontownGlobals.TOT_REWARD_END_OFFSET_AMOUNT + + if not av: + self.notify.warning( + 'Tried to send milestone feedback to av %s, but they left' % avId) + else: + #av.b_setCheesyEffect(OTPGlobals.CESnowMan, 0, (time.time()/60)+1) + av.b_setCheesyEffect(OTPGlobals.CESnowMan, 0, endTime/60) + + def huntGoalAlreadyFound(self, avId): + """ + This goal has already been found + """ + av = self.air.doId2do.get(avId) + if not av: + self.notify.warning( + 'Tried to send goal feedback to av %s, but they left' % avId) + else: + av.sendUpdate('winterCarolingTargetMet', [0]) + + def huntGoalFound(self, avId, goal): + """ + One of the goals in the milestone were found, + so we reward the toon. + """ + av = self.air.doId2do.get(avId) + # Do all the updates at once + av.addMoney(ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT) + self.avatarCompletedGoal(avId, goal) + # Start jellybean reward effect + av.sendUpdate('winterCarolingTargetMet', [ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT]) \ No newline at end of file diff --git a/toontown/effects/BingoManagerAI.py b/toontown/effects/BingoManagerAI.py new file mode 100644 index 0000000..4bfa0e7 --- /dev/null +++ b/toontown/effects/BingoManagerAI.py @@ -0,0 +1,564 @@ +################################################################# +# class: BingoManagerAI.py +# +# Purpose: Manages the Bingo Night Holiday for all ponds in all +# hoods. It generates PondBingoManagerAI objects for +# every pond and shuts them down respectively. In +# addition, it should handle all Stat collection such +# as top jackpot of the night, top bingo players, and +# so forth. +# +# Note: Eventually, this will derive from the HolidayBase class +# and run each and ever Bingo Night, whenever that has +# been decided upon. +################################################################# + +################################################################# +# Direct Specific Modules +################################################################# +from direct.distributed import DistributedObjectAI +from direct.distributed.ClockDelta import * +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import PythonUtil +from direct.task import Task + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.estate import DistributedEstateAI +from toontown.fishing import BingoGlobals +from toontown.fishing import DistributedFishingPondAI +from toontown.fishing import DistributedPondBingoManagerAI +from direct.showbase import RandomNumGen +from toontown.toonbase import ToontownGlobals +from toontown.hood import ZoneUtil + +################################################################# +# Python Specific Modules +################################################################# +import pickle +import os +import time + +################################################################# +# Globals and Constants +################################################################# +TTG = ToontownGlobals +BG = BingoGlobals + +class BingoManagerAI(object): +# __metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory("BingoManagerAI") + #notify.setDebug(True) + #notify.setInfo(True) + serverDataFolder = simbase.config.GetString('server-data-folder', "dependencies/backups/bingo") + + DefaultReward = { TTG.DonaldsDock: [BG.MIN_SUPER_JACKPOT, 1], + TTG.ToontownCentral: [BG.MIN_SUPER_JACKPOT, 1], + TTG.TheBrrrgh: [BG.MIN_SUPER_JACKPOT, 1], + TTG.MinniesMelodyland: [BG.MIN_SUPER_JACKPOT, 1], + TTG.DaisyGardens: [BG.MIN_SUPER_JACKPOT, 1], + TTG.DonaldsDreamland: [BG.MIN_SUPER_JACKPOT, 1], + TTG.MyEstate: [BG.MIN_SUPER_JACKPOT, 1] } + + ############################################################ + # Method: __init__ + # Purpose: This method initializes the BingoManagerAI object + # and generates the PondBingoManagerAI. + # Input: air - The AI Repository. + # Output: None + ############################################################ + def __init__(self, air): + self.air = air + + # Dictionaries for quick reference to the DPMAI + self.doId2do = {} + self.zoneId2do = {} + self.hood2doIdList = { TTG.DonaldsDock: [], + TTG.ToontownCentral: [], + TTG.TheBrrrgh: [], + TTG.MinniesMelodyland: [], + TTG.DaisyGardens: [], + TTG.DonaldsDreamland: [], + TTG.MyEstate: [] } + + self.__hoodJackpots = {} + self.finalGame = BG.NORMAL_GAME + self.shard = str(air.districtId) + self.waitTaskName = 'waitForIntermission' + + # Generate the Pond Bingo Managers + self.generateBingoManagers() + + ############################################################ + # Method: start + # Purpose: This method "starts" each PondBingoManager for + # the Bingo Night Holidy. + # Input: None + # Output: None + ############################################################ + def start(self): + # Iterate through keys and change into "active" state + # for the pondBingoManagerAI + self.notify.info("Starting Bingo Night Event: %s" % (time.ctime())) + self.air.bingoMgr = self + # Determine current time so that we can gracefully handle + # an AI crash or reboot during Bingo Night. + currentMin = time.localtime()[4] + self.timeStamp = globalClockDelta.getRealNetworkTime() + initState = ((currentMin < BG.HOUR_BREAK_MIN) and ['Intro'] or ['Intermission'])[0] + + # CHEATS + #initState = 'Intermission' + for do in list(self.doId2do.values()): + do.startup(initState) + self.waitForIntermission() + + # tell everyone bingo night is starting + simbase.air.newsManager.bingoStart() + + ############################################################ + # Method: stop + # Purpose: This method begins the process of shutting down + # bingo night. It is called whenever the + # BingoNightHolidayAI is told to close for the + # evening. + # Input: None + # Output: None + ############################################################ + def stop(self): + self.__startCloseEvent() + + ############################################################ + # Method: __shutdown + # Purpose: This method performs the actual shutdown sequence + # for the pond bingo manager. By this point, all + # of the PondBingoManagerAIs should have shutdown + # so we can safely close. + # Input: None + # Output: None + ############################################################ + def shutdown(self): + self.notify.info('__shutdown: Shutting down BingoManager') + + # tell everyone bingo night is stopping + simbase.air.newsManager.bingoEnd() + + if self.doId2do: + #self.notify.warning('__shutdown: Not all PondBingoManagers have shutdown! Manual Shutdown for Memory sake.') + for bingoMgr in list(self.doId2do.values()): + self.notify.info("__shutdown: shutting down PondBinfoManagerAI in zone %s" % bingoMgr.zoneId) + bingoMgr.shutdown() + self.doId2do.clear() + del self.doId2do + + self.air.bingoMgr = None + del self.air + del self.__hoodJackpots + + ############################################################ + # Method: __resumeBingoNight + # Purpose: This method resumes Bingo Night after an + # an intermission has taken place. This should + # start on the hour. + # Input: None + # Output: None + ############################################################ + def __resumeBingoNight(self, task): + self.__hoodJackpots = self.load() + for bingoMgr in list(self.doId2do.values()): + if bingoMgr.isGenerated(): + if self.finalGame: + bingoMgr.setFinalGame(self.finalGame) + bingoMgr.resumeBingoNight() + timeToWait = BG.getGameTime(BG.BLOCKOUT_CARD) + BG.TIMEOUT_SESSION + 5.0 + taskMgr.doMethodLater(timeToWait, self.__handleSuperBingoClose, 'SuperBingoClose') + + # If we have another game after this, then do not want to generate a + # new task to wait for the next intermission. + return Task.done + + ############################################################ + # Method: __handleSuperBingoClose + # Purpose: This method is responsible for logging the + # current hood jackpot amounts to the .jackpot + # "database" file. In addition, it initiates the + # shutdown of the BingoManagerAI if the final + # game of the evening has been played. + # Input: task - a task that is spawned by a doMethodLater + # Output: None + ############################################################ + def __handleSuperBingoClose(self, task): + # Save Jackpot Data to File + self.notify.info("handleSuperBingoClose: Saving Hood Jackpots to DB") + self.notify.info("handleSuperBingoClose: hoodJackpots %s" %(self.__hoodJackpots)) + for hood in list(self.__hoodJackpots.keys()): + if self.__hoodJackpots[hood][1]: + self.__hoodJackpots[hood][0] += BG.ROLLOVER_AMOUNT + # clamp it if it exceeds jackpot total + if self.__hoodJackpots[hood][0] > BG.MAX_SUPER_JACKPOT: + self.__hoodJackpots[hood][0] = BG.MAX_SUPER_JACKPOT + else: + self.__hoodJackpots[hood][1] = BG.MIN_SUPER_JACKPOT + + taskMgr.remove(task) + self.save() + if self.finalGame: + self.shutdown() + return + + self.waitForIntermission() + + ############################################################ + # Method: __handleIntermission + # Purpose: This wrapper method tells the intermission to + # start. + # Input: task - a task that is spawned by a doMethodLater + # Output: None + ############################################################ + def __handleIntermission(self, task): + self.__startIntermission() + + ############################################################ + # Method: getIntermissionTime + # Purpose: This method returns the time of when an + # intermission began. It is meant to provide a + # fairly accurate time countdown for the clients. + # Input: None + # Output: returns the timestamp of intermission start + ############################################################ + def getIntermissionTime(self): + return self.timeStamp + + ############################################################ + # Method: __startIntermission + # Purpose: This method is responsible for starting the + # hourly intermission for bingo night. + # Input: None + # Output: None + ############################################################ + def __startIntermission(self): + for bingoMgr in list(self.doId2do.values()): + bingoMgr.setFinalGame(BG.INTERMISSION) + + if not self.finalGame: + currentTime = time.localtime() + currentMin = currentTime[4] + currentSec = currentTime[5] + + # Calculate time until the next hour + waitTime = (60-currentMin)*60 - currentSec + sec = (currentMin - BG.HOUR_BREAK_MIN)*60 + currentSec + self.timeStamp = globalClockDelta.getRealNetworkTime() - sec + self.notify.info('__startIntermission: Timestamp %s'%(self.timeStamp)) + else: + # In case someone should decide that bingo night does not end on the hour, ie 30 past, + # then this will allow a five minute intermission to sync up the PBMgrAIs for the + # final game. + waitTime = BG.HOUR_BREAK_SESSION + self.timeStamp = globalClockDelta.getRealNetworkTime() + + self.waitTaskName = 'waitForEndOfIntermission' + self.notify.info('__startIntermission: Waiting %s seconds until Bingo Night resumes.' %(waitTime)) + taskMgr.doMethodLater(waitTime, self.__resumeBingoNight, self.waitTaskName) + return Task.done + + ############################################################ + # Method: __waitForIntermission + # Purpose: This method is responsible for calculating the + # wait time for the hourly intermission for bingo + # night. + # Input: None + # Output: None + ############################################################ + def waitForIntermission(self): + currentTime = time.localtime() + currentMin = currentTime[4] + currentSec = currentTime[5] + + # Calculate Amount of time needed for one normal game of Bingo from the + # Waitcountdown all the way to the gameover. (in secs) + if currentMin >= BG.HOUR_BREAK_MIN: + # If the AI starts during bingo night and after the intermission start(a crash or scheduled downtime), + # then immediately start the intermission to sync all the clients up for the next hour. + self.__startIntermission() + else: + waitTime = ((BG.HOUR_BREAK_MIN - currentMin)*60) - currentSec + self.waitTaskName = 'waitForIntermission' + self.notify.info("Waiting %s seconds until Final Game of the Hour should be announced." % (waitTime)) + taskMgr.doMethodLater(waitTime, self.__handleIntermission, self.waitTaskName) + + ############################################################ + # Method: generateBingoManagers + # Purpose: This method creates a PondBingoManager for each + # pond that is found within the hoods. It searches + # through each hood for pond objects and generates + # the corresponding ManagerAI objects. + # Input: None + # Output: None + ############################################################ + def generateBingoManagers(self): + # Create DPBMAI for all ponds in all hoods. + for hood in self.air.hoods: + self.createPondBingoMgrAI(hood) + + # Create DPBMAI for every pond in every active estate. + for estateAI in list(self.air.estateMgr.estate.values()): + self.createPondBingoMgrAI(estateAI) + + ############################################################ + # Method: addDistObj + # Purpose: This method adds the newly created Distributed + # object to the BingoManagerAI doId2do list for + # easy reference. + # Input: distObj + # Output: None + ############################################################ + def addDistObj(self, distObj): + self.notify.debug("addDistObj: Adding %s : %s" % (distObj.getDoId(), distObj.zoneId)) + self.doId2do[distObj.getDoId()] = distObj + self.zoneId2do[distObj.zoneId] = distObj + + def __hoodToUse(self, zoneId): + hood = ZoneUtil.getCanonicalHoodId(zoneId) + if hood >= TTG.DynamicZonesBegin: + hood = TTG.MyEstate + + return hood + + ############################################################ + # Method: createPondBingoMgrAI + # Purpose: This method generates PBMgrAI instances for + # each pond found in the specified hood. A hood + # may be an estate or an actual hood. + # Input: hood - HoodDataAI or EstateAI object. + # dynamic - Will be 1 only if an Estate was generated + # after Bingo Night has started. + # Output: None + ############################################################ + def createPondBingoMgrAI(self, hood, dynamic=0): + if hood.fishingPonds == None: + self.notify.warning("createPondBingoMgrAI: hood doesn't have any ponds... were they deleted? %s" % hood) + return + + for pond in hood.fishingPonds: + # First, optain hood id based on zone id that the pond is located in. + hoodId = self.__hoodToUse(pond.zoneId) + if hoodId not in self.hood2doIdList: + # for now don't start it for minigolf zone and outdoor zone + continue + + bingoMgr = DistributedPondBingoManagerAI.DistributedPondBingoManagerAI(self.air, pond) + bingoMgr.generateWithRequired(pond.zoneId) + + self.addDistObj(bingoMgr) + if hasattr(hood, "addDistObj"): + hood.addDistObj(bingoMgr) + pond.setPondBingoManager(bingoMgr) + + # Add the PBMgrAI reference to the hood2doIdList. + self.hood2doIdList[hoodId].append(bingoMgr.getDoId()) + + # Dynamic if this method was called when an estate was generated after + # Bingo Night has started. + if dynamic: + self.startDynPondBingoMgrAI(bingoMgr) + + ############################################################ + # Method: startDynPondBingoMgrAI + # Purpose: This method determines what state a Dynamic + # Estate PBMgrAI should start in, and then it tells + # the PBMgrAI to start. + # Input: bingoMgr - PondBongoMgrAI Instance + # Output: None + ############################################################ + def startDynPondBingoMgrAI(self, bingoMgr): + currentMin = time.localtime()[4] + + # If the dynamic estate is generated before the intermission starts + # and it is not the final game of the night, then the PBMgrAI should start + # in the WaitCountdown state. Otherwise, it should start in the intermission + # state so that it can sync up with all of the other Estate PBMgrAIs for the + # super bingo game. + initState = (((currentMin < BG.HOUR_BREAK_MIN) and (not self.finalGame)) and ['WaitCountdown'] or ['Intermission'])[0] + bingoMgr.startup(initState) + + ############################################################ + # Method: removePondBingoMgrAI + # Purpose: This method generates PBMgrAI instances for + # each pond found in the specified hood. A hood + # may be an estate or an actual hood. + # Input: doId - the doId of the PBMgrAI that should be + # removed from the dictionaries. + # Output: None + ############################################################ + def removePondBingoMgrAI(self, doId): + if doId in self.doId2do: + zoneId = self.doId2do[doId].zoneId + self.notify.info('removePondBingoMgrAI: Removing PondBingoMgrAI %s' %(zoneId)) + hood = self.__hoodToUse(zoneId) + self.hood2doIdList[hood].remove(doId) + del self.zoneId2do[zoneId] + del self.doId2do[doId] + else: + self.notify.debug('removeBingoManager: Attempt to remove invalid PondBingoManager %s' % (doId)) + + ############################################################ + # Method: SetFishForPlayer + # Purpose: This method adds the newly created Distributed + # object to the BingoManagerAI doId2do list for + # easy reference. + # Input: distObj + # Output: None + ############################################################ + def setAvCatchForPondMgr(self, avId, zoneId, catch): + self.notify.info('setAvCatchForPondMgr: zoneId %s' %(zoneId)) + if zoneId in self.zoneId2do: + self.zoneId2do[zoneId].setAvCatch(avId, catch) + else: + self.notify.info('setAvCatchForPondMgr Failed: zoneId %s' %(zoneId)) + + ############################################################ + # Method: getFileName + # Purpose: This method constructs the jackpot filename for + # a particular shard. + # Input: None + # Output: returns jackpot filename + ############################################################ + def getFileName(self): + """Figure out the path to the saved state""" + f = "%s%s.jackpot" % (self.serverDataFolder, self.shard) + return f + + ############################################################ + # Method: saveTo + # Purpose: This method saves the current jackpot ammounts + # to the specified file. + # Input: file - file to save jackpot amounts + # Output: None + ############################################################ + def saveTo(self, file): + pickle.dump(self.__hoodJackpots, file) + + ############################################################ + # Method: save + # Purpose: This method determines where to save the jackpot + # amounts. + # Input: None + # Output: None + ############################################################ + def save(self): + """Save data to default location""" + try: + fileName = self.getFileName() + backup = fileName+ '.jbu' + if os.path.exists(fileName): + os.rename(fileName, backup) + + file = open(fileName, 'wb') + file.seek(0) + self.saveTo(file) + file.close() + if os.path.exists(backup): + os.remove(backup) + except EnvironmentError: + self.notify.warning(str(sys.exc_info()[1])) + + ############################################################ + # Method: loadFrom + # Purpose: This method loads the jackpot amounts from the + # specified file. + # Input: File - file to load amount from + # Output: returns a dictionary of the jackpots for this shard + ############################################################ + def loadFrom(self, file): + # Default Jackpot Amount + jackpots = self.DefaultReward + try: + jackpots = pickle.load(file) + except EOFError: + pass + return jackpots + + ############################################################ + # Method: load + # Purpose: This method determines where to load the jackpot + # amounts. + # Input: None + # Output: None + ############################################################ + def load(self): + """Load Jackpot data from default location""" + fileName = self.getFileName() + try: + file = open(fileName+'.jbu', 'rb') + if os.path.exists(fileName): + os.remove(fileName) + except IOError: + try: + file = open(fileName) + except IOError: + # Default Jackpot Amount + return self.DefaultReward + + file.seek(0) + jackpots = self.loadFrom(file) + file.close() + return jackpots + + ############################################################ + # Method: getSuperJackpot + # Purpose: This method returns the super jackpot amount for + # the specified zone. It calculates which hood + # the zone is in and returns the shared jackpot + # amount for that hood. + # Input: zoneId - retrieve jackpot for this zone's hood + # Output: returns jackpot for hood that zoneid is found in + ############################################################ + def getSuperJackpot(self, zoneId): + hood = self.__hoodToUse(zoneId) + self.notify.info('getSuperJackpot: hoodJackpots %s \t hood %s' % (self.__hoodJackpots, hood)) + return self.__hoodJackpots.get(hood, [BG.MIN_SUPER_JACKPOT])[0] + + ############################################################ + # Method: __startCloseEvent + # Purpose: This method starts to close Bingo Night down. One + # more super card game will be played at the end + # of the hour(unless the times are changed). + # Input: None + # Output: None + ############################################################ + def __startCloseEvent(self): + self.finalGame = BG.CLOSE_EVENT + + if self.waitTaskName == 'waitForIntermission': + taskMgr.remove(self.waitTaskName) + self.__startIntermission() + + ############################################################ + # Method: handleSuperBingoWin + # Purpose: This method handles a victory when a super + # bingo game has been one. It updates the jackpot + # amount and tells each of the other ponds in that + # hood that they did not win. + # Input: zoneId - pond who won the bingo game. + # Output: None + ############################################################ + def handleSuperBingoWin(self, zoneId): + # Reset the Jackpot and unmark the dirty bit. + + hood = self.__hoodToUse(zoneId) + self.__hoodJackpots[hood][0] = self.DefaultReward[hood][0] + self.__hoodJackpots[hood][1] = 0 + + # tell everyone who won + #simbase.air.newsManager.bingoWin(zoneId) + + # Tell the other ponds that they did not win and should handle the loss + for doId in self.hood2doIdList[hood]: + distObj = self.doId2do[doId] + if distObj.zoneId != zoneId: + self.notify.info("handleSuperBingoWin: Did not win in zone %s" %(distObj.zoneId)) + distObj.handleSuperBingoLoss() + + diff --git a/toontown/effects/BingoNightHolidayAI.py b/toontown/effects/BingoNightHolidayAI.py new file mode 100644 index 0000000..0e26621 --- /dev/null +++ b/toontown/effects/BingoNightHolidayAI.py @@ -0,0 +1,89 @@ +################################################################# +# class: BingoNightHolidayAI.py +# +# Purpose: Manages the Bingo Night Holiday for all ponds in all +# hoods. +# Note: The Holiday Manager System(HMS) deletes each Holiday AI +# instance when the particular Holiday Expires. Unfortunately,this +# sort of functionality is not ideal for the BingoManagerAI +# class because we want to allow players to finish a final +# game of bingo before the BingoManagerAI shuts down. +# +# In order to prevent the BingoManagerAI from shutting down +# unexpectantly in the middle of a game, we provide this +# class to act as a "buffer" between the HSM +# and the BingoManagerAI. This class is created +# and destroyed by the HMS. When the HMS tells this class +# to start, it instantiates a BingoManagerAI object to +# run the actual Bingo Night Holiday. +# +# When the stop call is received, it tells the BingoManagerAI +# to stop after the next game and then it removes the reference +# to the BingoManagerAI. A reference to the BingoManagerAI still +# remains in the AIR so that the BingoManagerAI can finish +# the final Bingo Night Games before it deletes itself. +################################################################# + +################################################################# +# Direct Specific Modules +################################################################# +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import PythonUtil +from direct.task import Task + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai import HolidayBaseAI +from toontown.fishing import BingoGlobals +from toontown.fishing import BingoManagerAI + +################################################################# +# Python Specific Modules +################################################################# +import time + +class BingoNightHolidayAI(HolidayBaseAI.HolidayBaseAI): + notify = DirectNotifyGlobal.directNotify.newCategory('BingoNightHolidayAI') + ############################################################ + # Method: __init__ + # Purpose: This method initializes the HolidayBaseAI + # base class. + # Input: air - The AI Repository. + # Output: None + ############################################################ + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + ############################################################ + # Method: start + # Purpose: This method instantiates a BingoManagerAI and + # tells it to start up bingo night. + # Input: None + # Output: None + ############################################################ + def start(self): + + if self.air.bingoMgr: + raise PythonUtil.SingletonError("Bingo Manager already Exists! DO NOT RUN HOLIDAY!!") + else: + self.notify.info('Starting BingoNight Holiday: %s' % (time.ctime())) + self.bingoMgr = BingoManagerAI.BingoManagerAI(self.air) + self.bingoMgr.start() + + ############################################################ + # Method: start + # Purpose: This method tells the BingoManagerAI to shutdown + # and removes the reference. The BingoManagerAI + # does not actually shutdown until it finish all + # the PBMgrAIs have done so. The AIR maintains a + # reference to the BingoManagerAI so this method + # does not actually delete it. + # Input: None + # Output: None + ############################################################ + def stop(self): + if self.bingoMgr: + self.notify.info('stop: Tell the BingoManagerAI to stop BingoNight Holiday - %s' %(time.ctime())) + self.bingoMgr.stop() + del self.bingoMgr diff --git a/toontown/effects/FireworkManagerAI.py b/toontown/effects/FireworkManagerAI.py new file mode 100644 index 0000000..71ade48 --- /dev/null +++ b/toontown/effects/FireworkManagerAI.py @@ -0,0 +1,126 @@ +from direct.directnotify import DirectNotifyGlobal +import random +from direct.task import Task +from . import DistributedFireworkShowAI +from toontown.ai import HolidayBaseAI +from . import FireworkShow +from toontown.toonbase.ToontownGlobals import DonaldsDock, ToontownCentral, \ + TheBrrrgh, MinniesMelodyland, DaisyGardens, OutdoorZone, GoofySpeedway, DonaldsDreamland +import time + +class FireworkManagerAI(HolidayBaseAI.HolidayBaseAI): + """ + Manages Fireworks holidays + """ + + notify = DirectNotifyGlobal.directNotify.newCategory('FireworkManagerAI') + + zoneToStyleDict = { + # Donald's Dock + DonaldsDock : 5, + # Toontown Central + ToontownCentral : 0, + # The Brrrgh + TheBrrrgh : 4, + # Minnie's Melodyland + MinniesMelodyland : 3, + # Daisy Gardens + DaisyGardens : 1, + # Acorn Acres + OutdoorZone : 0, + # GS + GoofySpeedway : 0, + # Donald's Dreamland + DonaldsDreamland : 2, + } + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + # Dict from zone to DistFireworkShow objects + self.fireworkShows = {} + self.waitTaskName = 'waitStartFireworkShows' + + def start(self): + self.notify.info("Starting firework holiday: %s" % (time.ctime())) + self.waitForNextShow() + + def stop(self): + self.notify.info("Stopping firework holiday: %s" % (time.ctime())) + taskMgr.remove(self.waitTaskName) + self.stopAllShows() + + def startAllShows(self, task): + for hood in self.air.hoods: + showType = self.zoneToStyleDict.get(hood.canonicalHoodId) + if showType is not None: + self.startShow(hood.zoneId, showType) + + self.waitForNextShow() + return Task.done + + def waitForNextShow(self): + currentTime = time.localtime() + currentMin = currentTime[4] + currentSec = currentTime[5] + waitTime = ((60 - currentMin) * 60) - currentSec + self.notify.debug("Waiting %s seconds until next show" % (waitTime)) + taskMgr.doMethodLater(waitTime, self.startAllShows, self.waitTaskName) + + def startShow(self, zone, showType = -1, magicWord = 0): + """ + Start a show of showType in this zone. + Returns 1 if a show was successfully started. + Warns and returns 0 if a show was already running in this zone. + There can only be one show per zone. + """ + if zone in self.fireworkShows: + self.notify.warning("startShow: already running a show in zone: %s" % (zone)) + return 0 + self.notify.debug("startShow: zone: %s showType: %s" % (zone, showType)) + # Create a show, passing ourselves in so it can tell us when + # the show is over + show = DistributedFireworkShowAI.DistributedFireworkShowAI(self.air, self) + show.generateWithRequired(zone) + self.fireworkShows[zone] = show + # Currently needed to support legacy fireworks + if simbase.air.config.GetBool('want-old-fireworks', 0) or magicWord == 1: + show.d_startShow(showType, showType) + else: + show.d_startShow(self.holidayId, showType) + # Success! + return 1 + + def stopShow(self, zone): + """ + Stop a firework show in this zone. + Returns 1 if it did stop a show, warns and returns 0 if there is not one + """ + if zone not in self.fireworkShows: + self.notify.warning("stopShow: no show running in zone: %s" % (zone)) + return 0 + self.notify.debug("stopShow: zone: %s" % (zone)) + show = self.fireworkShows[zone] + del self.fireworkShows[zone] + show.requestDelete() + # Success! + return 1 + + def stopAllShows(self): + """ + Stop all firework shows this manager knows about in all zones. + Returns number of shows stopped by this command. + """ + numStopped = 0 + for zone, show in list(self.fireworkShows.items()): + self.notify.debug("stopAllShows: zone: %s" % (zone)) + show.requestDelete() + numStopped += 1 + self.fireworkShows.clear() + return numStopped + + def isShowRunning(self, zone): + """ + Is there currently a show running in this zone? + """ + return zone in self.fireworkShows + diff --git a/toontown/fishing/BingoManagerAI.py b/toontown/fishing/BingoManagerAI.py new file mode 100644 index 0000000..4bfa0e7 --- /dev/null +++ b/toontown/fishing/BingoManagerAI.py @@ -0,0 +1,564 @@ +################################################################# +# class: BingoManagerAI.py +# +# Purpose: Manages the Bingo Night Holiday for all ponds in all +# hoods. It generates PondBingoManagerAI objects for +# every pond and shuts them down respectively. In +# addition, it should handle all Stat collection such +# as top jackpot of the night, top bingo players, and +# so forth. +# +# Note: Eventually, this will derive from the HolidayBase class +# and run each and ever Bingo Night, whenever that has +# been decided upon. +################################################################# + +################################################################# +# Direct Specific Modules +################################################################# +from direct.distributed import DistributedObjectAI +from direct.distributed.ClockDelta import * +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import PythonUtil +from direct.task import Task + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.estate import DistributedEstateAI +from toontown.fishing import BingoGlobals +from toontown.fishing import DistributedFishingPondAI +from toontown.fishing import DistributedPondBingoManagerAI +from direct.showbase import RandomNumGen +from toontown.toonbase import ToontownGlobals +from toontown.hood import ZoneUtil + +################################################################# +# Python Specific Modules +################################################################# +import pickle +import os +import time + +################################################################# +# Globals and Constants +################################################################# +TTG = ToontownGlobals +BG = BingoGlobals + +class BingoManagerAI(object): +# __metaclass__ = PythonUtil.Singleton + notify = DirectNotifyGlobal.directNotify.newCategory("BingoManagerAI") + #notify.setDebug(True) + #notify.setInfo(True) + serverDataFolder = simbase.config.GetString('server-data-folder', "dependencies/backups/bingo") + + DefaultReward = { TTG.DonaldsDock: [BG.MIN_SUPER_JACKPOT, 1], + TTG.ToontownCentral: [BG.MIN_SUPER_JACKPOT, 1], + TTG.TheBrrrgh: [BG.MIN_SUPER_JACKPOT, 1], + TTG.MinniesMelodyland: [BG.MIN_SUPER_JACKPOT, 1], + TTG.DaisyGardens: [BG.MIN_SUPER_JACKPOT, 1], + TTG.DonaldsDreamland: [BG.MIN_SUPER_JACKPOT, 1], + TTG.MyEstate: [BG.MIN_SUPER_JACKPOT, 1] } + + ############################################################ + # Method: __init__ + # Purpose: This method initializes the BingoManagerAI object + # and generates the PondBingoManagerAI. + # Input: air - The AI Repository. + # Output: None + ############################################################ + def __init__(self, air): + self.air = air + + # Dictionaries for quick reference to the DPMAI + self.doId2do = {} + self.zoneId2do = {} + self.hood2doIdList = { TTG.DonaldsDock: [], + TTG.ToontownCentral: [], + TTG.TheBrrrgh: [], + TTG.MinniesMelodyland: [], + TTG.DaisyGardens: [], + TTG.DonaldsDreamland: [], + TTG.MyEstate: [] } + + self.__hoodJackpots = {} + self.finalGame = BG.NORMAL_GAME + self.shard = str(air.districtId) + self.waitTaskName = 'waitForIntermission' + + # Generate the Pond Bingo Managers + self.generateBingoManagers() + + ############################################################ + # Method: start + # Purpose: This method "starts" each PondBingoManager for + # the Bingo Night Holidy. + # Input: None + # Output: None + ############################################################ + def start(self): + # Iterate through keys and change into "active" state + # for the pondBingoManagerAI + self.notify.info("Starting Bingo Night Event: %s" % (time.ctime())) + self.air.bingoMgr = self + # Determine current time so that we can gracefully handle + # an AI crash or reboot during Bingo Night. + currentMin = time.localtime()[4] + self.timeStamp = globalClockDelta.getRealNetworkTime() + initState = ((currentMin < BG.HOUR_BREAK_MIN) and ['Intro'] or ['Intermission'])[0] + + # CHEATS + #initState = 'Intermission' + for do in list(self.doId2do.values()): + do.startup(initState) + self.waitForIntermission() + + # tell everyone bingo night is starting + simbase.air.newsManager.bingoStart() + + ############################################################ + # Method: stop + # Purpose: This method begins the process of shutting down + # bingo night. It is called whenever the + # BingoNightHolidayAI is told to close for the + # evening. + # Input: None + # Output: None + ############################################################ + def stop(self): + self.__startCloseEvent() + + ############################################################ + # Method: __shutdown + # Purpose: This method performs the actual shutdown sequence + # for the pond bingo manager. By this point, all + # of the PondBingoManagerAIs should have shutdown + # so we can safely close. + # Input: None + # Output: None + ############################################################ + def shutdown(self): + self.notify.info('__shutdown: Shutting down BingoManager') + + # tell everyone bingo night is stopping + simbase.air.newsManager.bingoEnd() + + if self.doId2do: + #self.notify.warning('__shutdown: Not all PondBingoManagers have shutdown! Manual Shutdown for Memory sake.') + for bingoMgr in list(self.doId2do.values()): + self.notify.info("__shutdown: shutting down PondBinfoManagerAI in zone %s" % bingoMgr.zoneId) + bingoMgr.shutdown() + self.doId2do.clear() + del self.doId2do + + self.air.bingoMgr = None + del self.air + del self.__hoodJackpots + + ############################################################ + # Method: __resumeBingoNight + # Purpose: This method resumes Bingo Night after an + # an intermission has taken place. This should + # start on the hour. + # Input: None + # Output: None + ############################################################ + def __resumeBingoNight(self, task): + self.__hoodJackpots = self.load() + for bingoMgr in list(self.doId2do.values()): + if bingoMgr.isGenerated(): + if self.finalGame: + bingoMgr.setFinalGame(self.finalGame) + bingoMgr.resumeBingoNight() + timeToWait = BG.getGameTime(BG.BLOCKOUT_CARD) + BG.TIMEOUT_SESSION + 5.0 + taskMgr.doMethodLater(timeToWait, self.__handleSuperBingoClose, 'SuperBingoClose') + + # If we have another game after this, then do not want to generate a + # new task to wait for the next intermission. + return Task.done + + ############################################################ + # Method: __handleSuperBingoClose + # Purpose: This method is responsible for logging the + # current hood jackpot amounts to the .jackpot + # "database" file. In addition, it initiates the + # shutdown of the BingoManagerAI if the final + # game of the evening has been played. + # Input: task - a task that is spawned by a doMethodLater + # Output: None + ############################################################ + def __handleSuperBingoClose(self, task): + # Save Jackpot Data to File + self.notify.info("handleSuperBingoClose: Saving Hood Jackpots to DB") + self.notify.info("handleSuperBingoClose: hoodJackpots %s" %(self.__hoodJackpots)) + for hood in list(self.__hoodJackpots.keys()): + if self.__hoodJackpots[hood][1]: + self.__hoodJackpots[hood][0] += BG.ROLLOVER_AMOUNT + # clamp it if it exceeds jackpot total + if self.__hoodJackpots[hood][0] > BG.MAX_SUPER_JACKPOT: + self.__hoodJackpots[hood][0] = BG.MAX_SUPER_JACKPOT + else: + self.__hoodJackpots[hood][1] = BG.MIN_SUPER_JACKPOT + + taskMgr.remove(task) + self.save() + if self.finalGame: + self.shutdown() + return + + self.waitForIntermission() + + ############################################################ + # Method: __handleIntermission + # Purpose: This wrapper method tells the intermission to + # start. + # Input: task - a task that is spawned by a doMethodLater + # Output: None + ############################################################ + def __handleIntermission(self, task): + self.__startIntermission() + + ############################################################ + # Method: getIntermissionTime + # Purpose: This method returns the time of when an + # intermission began. It is meant to provide a + # fairly accurate time countdown for the clients. + # Input: None + # Output: returns the timestamp of intermission start + ############################################################ + def getIntermissionTime(self): + return self.timeStamp + + ############################################################ + # Method: __startIntermission + # Purpose: This method is responsible for starting the + # hourly intermission for bingo night. + # Input: None + # Output: None + ############################################################ + def __startIntermission(self): + for bingoMgr in list(self.doId2do.values()): + bingoMgr.setFinalGame(BG.INTERMISSION) + + if not self.finalGame: + currentTime = time.localtime() + currentMin = currentTime[4] + currentSec = currentTime[5] + + # Calculate time until the next hour + waitTime = (60-currentMin)*60 - currentSec + sec = (currentMin - BG.HOUR_BREAK_MIN)*60 + currentSec + self.timeStamp = globalClockDelta.getRealNetworkTime() - sec + self.notify.info('__startIntermission: Timestamp %s'%(self.timeStamp)) + else: + # In case someone should decide that bingo night does not end on the hour, ie 30 past, + # then this will allow a five minute intermission to sync up the PBMgrAIs for the + # final game. + waitTime = BG.HOUR_BREAK_SESSION + self.timeStamp = globalClockDelta.getRealNetworkTime() + + self.waitTaskName = 'waitForEndOfIntermission' + self.notify.info('__startIntermission: Waiting %s seconds until Bingo Night resumes.' %(waitTime)) + taskMgr.doMethodLater(waitTime, self.__resumeBingoNight, self.waitTaskName) + return Task.done + + ############################################################ + # Method: __waitForIntermission + # Purpose: This method is responsible for calculating the + # wait time for the hourly intermission for bingo + # night. + # Input: None + # Output: None + ############################################################ + def waitForIntermission(self): + currentTime = time.localtime() + currentMin = currentTime[4] + currentSec = currentTime[5] + + # Calculate Amount of time needed for one normal game of Bingo from the + # Waitcountdown all the way to the gameover. (in secs) + if currentMin >= BG.HOUR_BREAK_MIN: + # If the AI starts during bingo night and after the intermission start(a crash or scheduled downtime), + # then immediately start the intermission to sync all the clients up for the next hour. + self.__startIntermission() + else: + waitTime = ((BG.HOUR_BREAK_MIN - currentMin)*60) - currentSec + self.waitTaskName = 'waitForIntermission' + self.notify.info("Waiting %s seconds until Final Game of the Hour should be announced." % (waitTime)) + taskMgr.doMethodLater(waitTime, self.__handleIntermission, self.waitTaskName) + + ############################################################ + # Method: generateBingoManagers + # Purpose: This method creates a PondBingoManager for each + # pond that is found within the hoods. It searches + # through each hood for pond objects and generates + # the corresponding ManagerAI objects. + # Input: None + # Output: None + ############################################################ + def generateBingoManagers(self): + # Create DPBMAI for all ponds in all hoods. + for hood in self.air.hoods: + self.createPondBingoMgrAI(hood) + + # Create DPBMAI for every pond in every active estate. + for estateAI in list(self.air.estateMgr.estate.values()): + self.createPondBingoMgrAI(estateAI) + + ############################################################ + # Method: addDistObj + # Purpose: This method adds the newly created Distributed + # object to the BingoManagerAI doId2do list for + # easy reference. + # Input: distObj + # Output: None + ############################################################ + def addDistObj(self, distObj): + self.notify.debug("addDistObj: Adding %s : %s" % (distObj.getDoId(), distObj.zoneId)) + self.doId2do[distObj.getDoId()] = distObj + self.zoneId2do[distObj.zoneId] = distObj + + def __hoodToUse(self, zoneId): + hood = ZoneUtil.getCanonicalHoodId(zoneId) + if hood >= TTG.DynamicZonesBegin: + hood = TTG.MyEstate + + return hood + + ############################################################ + # Method: createPondBingoMgrAI + # Purpose: This method generates PBMgrAI instances for + # each pond found in the specified hood. A hood + # may be an estate or an actual hood. + # Input: hood - HoodDataAI or EstateAI object. + # dynamic - Will be 1 only if an Estate was generated + # after Bingo Night has started. + # Output: None + ############################################################ + def createPondBingoMgrAI(self, hood, dynamic=0): + if hood.fishingPonds == None: + self.notify.warning("createPondBingoMgrAI: hood doesn't have any ponds... were they deleted? %s" % hood) + return + + for pond in hood.fishingPonds: + # First, optain hood id based on zone id that the pond is located in. + hoodId = self.__hoodToUse(pond.zoneId) + if hoodId not in self.hood2doIdList: + # for now don't start it for minigolf zone and outdoor zone + continue + + bingoMgr = DistributedPondBingoManagerAI.DistributedPondBingoManagerAI(self.air, pond) + bingoMgr.generateWithRequired(pond.zoneId) + + self.addDistObj(bingoMgr) + if hasattr(hood, "addDistObj"): + hood.addDistObj(bingoMgr) + pond.setPondBingoManager(bingoMgr) + + # Add the PBMgrAI reference to the hood2doIdList. + self.hood2doIdList[hoodId].append(bingoMgr.getDoId()) + + # Dynamic if this method was called when an estate was generated after + # Bingo Night has started. + if dynamic: + self.startDynPondBingoMgrAI(bingoMgr) + + ############################################################ + # Method: startDynPondBingoMgrAI + # Purpose: This method determines what state a Dynamic + # Estate PBMgrAI should start in, and then it tells + # the PBMgrAI to start. + # Input: bingoMgr - PondBongoMgrAI Instance + # Output: None + ############################################################ + def startDynPondBingoMgrAI(self, bingoMgr): + currentMin = time.localtime()[4] + + # If the dynamic estate is generated before the intermission starts + # and it is not the final game of the night, then the PBMgrAI should start + # in the WaitCountdown state. Otherwise, it should start in the intermission + # state so that it can sync up with all of the other Estate PBMgrAIs for the + # super bingo game. + initState = (((currentMin < BG.HOUR_BREAK_MIN) and (not self.finalGame)) and ['WaitCountdown'] or ['Intermission'])[0] + bingoMgr.startup(initState) + + ############################################################ + # Method: removePondBingoMgrAI + # Purpose: This method generates PBMgrAI instances for + # each pond found in the specified hood. A hood + # may be an estate or an actual hood. + # Input: doId - the doId of the PBMgrAI that should be + # removed from the dictionaries. + # Output: None + ############################################################ + def removePondBingoMgrAI(self, doId): + if doId in self.doId2do: + zoneId = self.doId2do[doId].zoneId + self.notify.info('removePondBingoMgrAI: Removing PondBingoMgrAI %s' %(zoneId)) + hood = self.__hoodToUse(zoneId) + self.hood2doIdList[hood].remove(doId) + del self.zoneId2do[zoneId] + del self.doId2do[doId] + else: + self.notify.debug('removeBingoManager: Attempt to remove invalid PondBingoManager %s' % (doId)) + + ############################################################ + # Method: SetFishForPlayer + # Purpose: This method adds the newly created Distributed + # object to the BingoManagerAI doId2do list for + # easy reference. + # Input: distObj + # Output: None + ############################################################ + def setAvCatchForPondMgr(self, avId, zoneId, catch): + self.notify.info('setAvCatchForPondMgr: zoneId %s' %(zoneId)) + if zoneId in self.zoneId2do: + self.zoneId2do[zoneId].setAvCatch(avId, catch) + else: + self.notify.info('setAvCatchForPondMgr Failed: zoneId %s' %(zoneId)) + + ############################################################ + # Method: getFileName + # Purpose: This method constructs the jackpot filename for + # a particular shard. + # Input: None + # Output: returns jackpot filename + ############################################################ + def getFileName(self): + """Figure out the path to the saved state""" + f = "%s%s.jackpot" % (self.serverDataFolder, self.shard) + return f + + ############################################################ + # Method: saveTo + # Purpose: This method saves the current jackpot ammounts + # to the specified file. + # Input: file - file to save jackpot amounts + # Output: None + ############################################################ + def saveTo(self, file): + pickle.dump(self.__hoodJackpots, file) + + ############################################################ + # Method: save + # Purpose: This method determines where to save the jackpot + # amounts. + # Input: None + # Output: None + ############################################################ + def save(self): + """Save data to default location""" + try: + fileName = self.getFileName() + backup = fileName+ '.jbu' + if os.path.exists(fileName): + os.rename(fileName, backup) + + file = open(fileName, 'wb') + file.seek(0) + self.saveTo(file) + file.close() + if os.path.exists(backup): + os.remove(backup) + except EnvironmentError: + self.notify.warning(str(sys.exc_info()[1])) + + ############################################################ + # Method: loadFrom + # Purpose: This method loads the jackpot amounts from the + # specified file. + # Input: File - file to load amount from + # Output: returns a dictionary of the jackpots for this shard + ############################################################ + def loadFrom(self, file): + # Default Jackpot Amount + jackpots = self.DefaultReward + try: + jackpots = pickle.load(file) + except EOFError: + pass + return jackpots + + ############################################################ + # Method: load + # Purpose: This method determines where to load the jackpot + # amounts. + # Input: None + # Output: None + ############################################################ + def load(self): + """Load Jackpot data from default location""" + fileName = self.getFileName() + try: + file = open(fileName+'.jbu', 'rb') + if os.path.exists(fileName): + os.remove(fileName) + except IOError: + try: + file = open(fileName) + except IOError: + # Default Jackpot Amount + return self.DefaultReward + + file.seek(0) + jackpots = self.loadFrom(file) + file.close() + return jackpots + + ############################################################ + # Method: getSuperJackpot + # Purpose: This method returns the super jackpot amount for + # the specified zone. It calculates which hood + # the zone is in and returns the shared jackpot + # amount for that hood. + # Input: zoneId - retrieve jackpot for this zone's hood + # Output: returns jackpot for hood that zoneid is found in + ############################################################ + def getSuperJackpot(self, zoneId): + hood = self.__hoodToUse(zoneId) + self.notify.info('getSuperJackpot: hoodJackpots %s \t hood %s' % (self.__hoodJackpots, hood)) + return self.__hoodJackpots.get(hood, [BG.MIN_SUPER_JACKPOT])[0] + + ############################################################ + # Method: __startCloseEvent + # Purpose: This method starts to close Bingo Night down. One + # more super card game will be played at the end + # of the hour(unless the times are changed). + # Input: None + # Output: None + ############################################################ + def __startCloseEvent(self): + self.finalGame = BG.CLOSE_EVENT + + if self.waitTaskName == 'waitForIntermission': + taskMgr.remove(self.waitTaskName) + self.__startIntermission() + + ############################################################ + # Method: handleSuperBingoWin + # Purpose: This method handles a victory when a super + # bingo game has been one. It updates the jackpot + # amount and tells each of the other ponds in that + # hood that they did not win. + # Input: zoneId - pond who won the bingo game. + # Output: None + ############################################################ + def handleSuperBingoWin(self, zoneId): + # Reset the Jackpot and unmark the dirty bit. + + hood = self.__hoodToUse(zoneId) + self.__hoodJackpots[hood][0] = self.DefaultReward[hood][0] + self.__hoodJackpots[hood][1] = 0 + + # tell everyone who won + #simbase.air.newsManager.bingoWin(zoneId) + + # Tell the other ponds that they did not win and should handle the loss + for doId in self.hood2doIdList[hood]: + distObj = self.doId2do[doId] + if distObj.zoneId != zoneId: + self.notify.info("handleSuperBingoWin: Did not win in zone %s" %(distObj.zoneId)) + distObj.handleSuperBingoLoss() + + diff --git a/toontown/fishing/BingoNightHolidayAI.py b/toontown/fishing/BingoNightHolidayAI.py new file mode 100644 index 0000000..0e26621 --- /dev/null +++ b/toontown/fishing/BingoNightHolidayAI.py @@ -0,0 +1,89 @@ +################################################################# +# class: BingoNightHolidayAI.py +# +# Purpose: Manages the Bingo Night Holiday for all ponds in all +# hoods. +# Note: The Holiday Manager System(HMS) deletes each Holiday AI +# instance when the particular Holiday Expires. Unfortunately,this +# sort of functionality is not ideal for the BingoManagerAI +# class because we want to allow players to finish a final +# game of bingo before the BingoManagerAI shuts down. +# +# In order to prevent the BingoManagerAI from shutting down +# unexpectantly in the middle of a game, we provide this +# class to act as a "buffer" between the HSM +# and the BingoManagerAI. This class is created +# and destroyed by the HMS. When the HMS tells this class +# to start, it instantiates a BingoManagerAI object to +# run the actual Bingo Night Holiday. +# +# When the stop call is received, it tells the BingoManagerAI +# to stop after the next game and then it removes the reference +# to the BingoManagerAI. A reference to the BingoManagerAI still +# remains in the AIR so that the BingoManagerAI can finish +# the final Bingo Night Games before it deletes itself. +################################################################# + +################################################################# +# Direct Specific Modules +################################################################# +from direct.directnotify import DirectNotifyGlobal +from otp.otpbase import PythonUtil +from direct.task import Task + +################################################################# +# Toontown Specific Modules +################################################################# +from toontown.ai import HolidayBaseAI +from toontown.fishing import BingoGlobals +from toontown.fishing import BingoManagerAI + +################################################################# +# Python Specific Modules +################################################################# +import time + +class BingoNightHolidayAI(HolidayBaseAI.HolidayBaseAI): + notify = DirectNotifyGlobal.directNotify.newCategory('BingoNightHolidayAI') + ############################################################ + # Method: __init__ + # Purpose: This method initializes the HolidayBaseAI + # base class. + # Input: air - The AI Repository. + # Output: None + ############################################################ + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + ############################################################ + # Method: start + # Purpose: This method instantiates a BingoManagerAI and + # tells it to start up bingo night. + # Input: None + # Output: None + ############################################################ + def start(self): + + if self.air.bingoMgr: + raise PythonUtil.SingletonError("Bingo Manager already Exists! DO NOT RUN HOLIDAY!!") + else: + self.notify.info('Starting BingoNight Holiday: %s' % (time.ctime())) + self.bingoMgr = BingoManagerAI.BingoManagerAI(self.air) + self.bingoMgr.start() + + ############################################################ + # Method: start + # Purpose: This method tells the BingoManagerAI to shutdown + # and removes the reference. The BingoManagerAI + # does not actually shutdown until it finish all + # the PBMgrAIs have done so. The AIR maintains a + # reference to the BingoManagerAI so this method + # does not actually delete it. + # Input: None + # Output: None + ############################################################ + def stop(self): + if self.bingoMgr: + self.notify.info('stop: Tell the BingoManagerAI to stop BingoNight Holiday - %s' %(time.ctime())) + self.bingoMgr.stop() + del self.bingoMgr diff --git a/toontown/scavengerhunt/SHtest.py b/toontown/scavengerhunt/SHtest.py new file mode 100644 index 0000000..1cacdbf --- /dev/null +++ b/toontown/scavengerhunt/SHtest.py @@ -0,0 +1,60 @@ +###################################################################### +# This file is meant for unit testing the ScavengerHunt system. # +# It can also be used to demonstrate how the system should be # +# used. # +# # +# It's meant to be run after any changes are made to the system. # +# # +# Usage: python SHtest.py # +###################################################################### + +from .ScavengerHuntBase import ScavengerHuntBase +import unittest,copy + +hunt = ScavengerHuntBase(scavengerHuntId = 12,scavengerHuntType = 3) +hunt.defineGoals(list(range(1,6))) +hunt.defineMilestones([[0,list(range(1,4))],[1,list(range(1,6))]]) + +class MilestoneTestCase(unittest.TestCase): + def testDefineGoals(self): + gc = set(range(1,6)) + self.assertEqual(hunt.goals,gc) + + def testDefineMilestones(self): + m = {} + + gc = list(range(1,4)) + m[frozenset(gc)] = 0 + + gc = list(range(1,6)) + m[frozenset(gc)] = 1 + + self.assertEqual(hunt.milestones,m) + + def testRecentMilestonesHit(self): + gc = list(range(1,4)) + m = hunt.getRecentMilestonesHit(gc,2) + self.assertEqual([0],m) + + gc = list(range(1,6)) + m = hunt.getRecentMilestonesHit(gc,2) + m.sort() + self.assertEqual([0,1],m) + + def testRecentMilestonesMissed(self): + gc = list(range(1,5)) + + m = hunt.getRecentMilestonesHit(gc,4) + self.assertEqual([],m) + + def testAllMilestonesHit(self): + gc = list(range(1,6)) + + m = hunt.getAllMilestonesHit(gc) + m.sort() + M = list(hunt.milestones.values()) + M.sort() + self.assertEqual(M,m) + +if __name__ == "__main__": + unittest.main() diff --git a/toontown/scavengerhunt/ScavengerHuntBase.py b/toontown/scavengerhunt/ScavengerHuntBase.py new file mode 100644 index 0000000..62b9ade --- /dev/null +++ b/toontown/scavengerhunt/ScavengerHuntBase.py @@ -0,0 +1,65 @@ +#from direct.directnotify import DirectNotifyGlobal +class ScavengerHuntBase: + """ + Base class for all hunts. There is enough functionality here + such that you shouldn't need to subclass it, though. + """ + + def __init__(self,scavengerHuntId, scavengerHuntType): + self.id = scavengerHuntId + self.type = scavengerHuntType + self.goals = set() + self.milestones = {} + + def defineGoals(self,goalIds): + """ + Accepts a list of Goal identifiers. This could be something as + simple as a range of integers corresponding to the goals in the + hunt. + """ + self.goals = set(goalIds) + + def defineMilestones(self,milestones = []): + """ + Accepts a list with items of the format: + [milestoneId,[goal1,goal2,goal3,...]] + """ + for id,stone in milestones: + self.milestones[frozenset(stone)] = id + + def getRecentMilestonesHit(self,goals,mostRecentGoal): + """ + Given a list of goals, and the most recent goal added to that + list, return a list of milestone ids which that latest goal would + trigger. + """ + milestones = [] + + for milestone in list(self.milestones.keys()): + if mostRecentGoal in milestone and milestone.issubset(goals): + milestones.append(self.milestones[milestone]) + return milestones + + + def getAllMilestonesHit(self,goals): + """ + Return a list of milestone ids which are satisfied by the Goals listed + in goals. + """ + milestones = [] + + for milestone in list(self.milestones.keys()): + if(milestone.issubset(goals)): + milestones.append(self.milestones[milestone]) + + return milestones + + + + + + + + + + diff --git a/toontown/scavengerhunt/Sources.pp b/toontown/scavengerhunt/Sources.pp new file mode 100644 index 0000000..a03ea8c --- /dev/null +++ b/toontown/scavengerhunt/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/toontown/scavengerhunt/__init__.py b/toontown/scavengerhunt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/suit/HolidaySuitInvasionManagerAI.py b/toontown/suit/HolidaySuitInvasionManagerAI.py new file mode 100644 index 0000000..ec140bd --- /dev/null +++ b/toontown/suit/HolidaySuitInvasionManagerAI.py @@ -0,0 +1,285 @@ +from direct.directnotify import DirectNotifyGlobal +from toontown.ai import HolidayBaseAI +from . import SuitInvasionManagerAI +from toontown.toonbase import ToontownGlobals + +class HolidaySuitInvasionManagerAI(HolidayBaseAI.HolidayBaseAI): + + notify = DirectNotifyGlobal.directNotify.newCategory('HolidaySuitInvasionManagerAI') + + def __init__(self, air, holidayId): + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + + def start(self): + # Stop any current invasion that might be happening by chance + if self.air.suitInvasionManager.getInvading(): + self.notify.info("Stopping current invasion to make room for holiday %s" % + (self.holidayId)) + self.air.suitInvasionManager.stopInvasion() + + if not simbase.config.GetBool('want-invasions', 1): + return 1 + + if (self.holidayId == ToontownGlobals.HALLOWEEN): + # Bloodsucker invasion on Halloween + cogType = 'b' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SKELECOG_INVASION): + # any cog will do + from . import SuitDNA + import random + cogType = random.choice(SuitDNA.suitHeadTypes) + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 1 + elif (self.holidayId == ToontownGlobals.MR_HOLLYWOOD_INVASION): + # Mr. Hollywood of course... + cogType = 'mh' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BOSSCOG_INVASION): + # any cog will do + from . import SuitDNA + import random + cogType = SuitDNA.getRandomSuitByDept('c') + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.MARCH_INVASION): + # Backstabbers... + cogType = 'bs' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.DECEMBER_INVASION): + # Sellbots... + cogType = 'cc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_1): + # Sellbot Surprise... cold caller + cogType = 'cc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_2 or \ + self.holidayId == ToontownGlobals.NAME_DROPPER_INVASION): + # Sellbot Surprise ... Name dropper + cogType = 'nd' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_3): + # Sellbot Surprise ... gladhander + cogType = 'gh' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_4 or \ + self.holidayId == ToontownGlobals.MOVER_AND_SHAKER_INVASION): + # Sellbot Surprise ... mover & shaker + cogType = 'ms' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_1): + # Cashbot Conundrum... Short Change + cogType = 'sc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_2 or \ + self.holidayId == ToontownGlobals.PENNY_PINCHER_INVASION): + # Cashbot Conundrum... Penny Pincher + cogType = 'pp' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_3): + # Cashbot Conundrum... Bean Counter + cogType = 'bc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_4 or \ + self.holidayId == ToontownGlobals.NUMBER_CRUNCHER_INVASION): + # Cashbot Conundrum... Number Cruncher + cogType = 'nc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_1): + # Lawbot Gambit... bottom feeder + cogType = 'bf' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_2 or \ + self.holidayId == ToontownGlobals.DOUBLE_TALKER_INVASION): + # Lawbot Gambit... double talker + cogType = 'dt' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_3 or \ + self.holidayId == ToontownGlobals.AMBULANCE_CHASER_INVASION): + # Lawbot Gambit... ambulance chaser + cogType = 'ac' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_4): + # Lawbot Gambit... back stabber + cogType = 'bs' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_1): + # The Trouble with Bossbots ... flunky + cogType = 'f' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_2): + # The Trouble with Bossbots ... pencil pusher + cogType = 'p' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_3 or \ + self.holidayId == ToontownGlobals.MICROMANAGER_INVASION): + # The Trouble with Bossbots ... micro manager + cogType = 'mm' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_4 or \ + self.holidayId == ToontownGlobals.DOWN_SIZER_INVASION ): + # The Trouble with Bossbots ... downsizer + cogType = 'ds' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.COLD_CALLER_INVASION): + cogType = 'cc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BEAN_COUNTER_INVASION): + cogType = 'bc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.DOUBLE_TALKER_INVASION): + # The Trouble with Bossbots ... downsizer + cogType = 'dt' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.DOWNSIZER_INVASION): + # The Trouble with Bossbots ... downsizer + cogType = 'ds' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.YES_MAN_INVASION): + # The Trouble with Bossbots ... yes man + cogType = 'ym' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TIGHTWAD_INVASION): + # tightwad + cogType = 'tw' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TELEMARKETER_INVASION): + # telemarketer + cogType = 'tm' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.HEADHUNTER_INVASION): + # head hunter + cogType = 'hh' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.SPINDOCTOR_INVASION): + # spin doctor + cogType = 'sd' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.MONEYBAGS_INVASION): + # money bags + cogType = 'mb' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.TWOFACES_INVASION): + # two faces + cogType = 'tf' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.MINGLER_INVASION): + # mingler + cogType = 'm' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LOANSHARK_INVASION): + # loan sharks + cogType = 'ls' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.CORPORATE_RAIDER_INVASION): + # corporate raider + cogType = 'cr' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.LEGAL_EAGLE_INVASION): + # legal eagle + cogType = 'le' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.ROBBER_BARON_INVASION): + # robber baron + cogType = 'rb' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BIG_WIG_INVASION): + # big wig + cogType = 'bw' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + elif (self.holidayId == ToontownGlobals.BIG_CHEESE_INVASION): + # big cheese + cogType = 'tbc' + # Max the number so they will not run out + numCogs = 1000000000 + skeleton = 0 + else: + self.notify.warning("Unrecognized holidayId: %s" % (self.holidayId)) + return 0 + + self.air.suitInvasionManager.startInvasion(cogType, numCogs, skeleton) + self.air.suitInvasionManager.waitForNextInvasion() + return 1 + + def stop(self): + # Holiday is over, stop the invasion if it happens to still be running + if self.air.suitInvasionManager.getInvading(): + self.notify.info("Prematurely stopping holiday invasion: %s" % (self.holidayId)) + self.air.suitInvasionManager.stopInvasion() diff --git a/toontown/suit/SuitInvasionManagerAI.py b/toontown/suit/SuitInvasionManagerAI.py index bb1231c..7fe5e5d 100644 --- a/toontown/suit/SuitInvasionManagerAI.py +++ b/toontown/suit/SuitInvasionManagerAI.py @@ -1,13 +1,151 @@ from direct.directnotify import DirectNotifyGlobal +from toontown.battle import SuitBattleGlobals +import random +from direct.task import Task class SuitInvasionManagerAI: + """ + Manages invasions of Suits + """ + notify = DirectNotifyGlobal.directNotify.newCategory('SuitInvasionManagerAI') def __init__(self, air): self.air = air + self.invading = 0 + self.cogType = None + self.skeleton = 0 + self.totalNumCogs = 0 + self.numCogsRemaining = 0 - def getInvadingCog(self): - return None, 0 + # Set of cog types to choose from See + # SuitBattleGlobals.SuitAttributes.keys() for all choices I did not + # put the highest level Cogs from each track in here to keep them + # special and only found in buildings. I threw in the Flunky just + # for fun. + self.invadingCogTypes = ( + # Corporate + 'f', # Flunky + 'hh', # Head Hunter + 'cr', # Corporate Raider + # Sales + 'tf', # Two-faced + 'm', # Mingler + # Money + 'mb', # Money Bags + 'ls', # Loan shark + # Legal + 'sd', # Spin Doctor + 'le', # Legal Eagle + ) + + # Picked from randomly how many cogs will invade + # This might need to be adjusted based on population(?) + self.invadingNumList = (1000, 2000, 3000, 4000) + + # Minimum time between invasions on this shard (in seconds) + # No more than 1 per 2 days + self.invasionMinDelay = 2 * 24 * 60 * 60 + # Maximum time between invasions on this shard (in seconds) + # At least once every 7 days + self.invasionMaxDelay = 7 * 24 * 60 * 60 + + # Kick off the first invasion + self.waitForNextInvasion() + + def delete(self): + taskMgr.remove(self.taskName("cogInvasionMgr")) + + def computeInvasionDelay(self): + # Compute the delay until the next invasion + return ((self.invasionMaxDelay - self.invasionMinDelay) * random.random() + + self.invasionMinDelay) + + def tryInvasionAndWaitForNext(self, task): + # Start the invasion if there is not one already + if self.getInvading(): + self.notify.warning("invasionTask: tried to start random invasion, but one is in progress") + else: + self.notify.info("invasionTask: starting random invasion") + cogType = random.choice(self.invadingCogTypes) + totalNumCogs = random.choice(self.invadingNumList) + self.startInvasion(cogType, totalNumCogs) + # In either case, fire off the next invasion + self.waitForNextInvasion() + return Task.done + + def waitForNextInvasion(self): + taskMgr.remove(self.taskName("cogInvasionMgr")) + delay = self.computeInvasionDelay() + self.notify.info("invasionTask: waiting %s seconds until next invasion" % delay) + taskMgr.doMethodLater(delay, self.tryInvasionAndWaitForNext, + self.taskName("cogInvasionMgr")) def getInvading(self): - return False + return self.invading + + def getCogType(self): + return self.cogType, self.isSkeleton + + def getNumCogsRemaining(self): + return self.numCogsRemaining + + def getTotalNumCogs(self): + return self.totalNumCogs + + def startInvasion(self, cogType, totalNumCogs, skeleton=0): + if self.invading: + self.notify.warning("startInvasion: already invading cogType: %s numCogsRemaining: %s" % + (cogType, self.numCogsRemaining)) + return 0 + if not SuitBattleGlobals.SuitAttributes.get(cogType): + self.notify.warning("startInvasion: unknown cogType: %s" % cogType) + return 0 + + self.notify.info("startInvasion: cogType: %s totalNumCogs: %s skeleton: %s" % + (cogType, totalNumCogs, skeleton)) + self.invading = 1 + self.cogType = cogType + self.isSkeleton = skeleton + self.totalNumCogs = totalNumCogs + self.numCogsRemaining = self.totalNumCogs + + # Tell the news manager that an invasion is beginning + self.air.newsManager.invasionBegin(self.cogType, self.totalNumCogs, self.isSkeleton) + + # Get rid of all the current cogs on the streets + # (except those already in battle, they can stay) + for suitPlanner in list(self.air.suitPlanners.values()): + suitPlanner.flySuits() + # Success! + return 1 + + def getInvadingCog(self): + if self.invading: + self.numCogsRemaining -= 1 + if self.numCogsRemaining <= 0: + self.stopInvasion() + self.notify.debug("getInvadingCog: returned cog: %s, num remaining: %s" % + (self.cogType, self.numCogsRemaining)) + return self.cogType, self.isSkeleton + else: + self.notify.debug("getInvadingCog: not currently invading") + return None, None + + def stopInvasion(self): + self.notify.info("stopInvasion: invasion is over now") + # Tell the news manager that an invasion is ending + self.air.newsManager.invasionEnd(self.cogType, 0, self.isSkeleton) + self.invading = 0 + self.cogType = None + self.isSkeleton = 0 + self.totalNumCogs = 0 + self.numCogsRemaining = 0 + # Get rid of all the current invasion cogs on the streets + # (except those already in battle, they can stay) + for suitPlanner in list(self.air.suitPlanners.values()): + suitPlanner.flySuits() + + # Need this here since this is not a distributed object + def taskName(self, taskString): + return (taskString + "-" + str(hash(self))) diff --git a/toontown/uberdog/DataStoreAIClient.py b/toontown/uberdog/DataStoreAIClient.py new file mode 100644 index 0000000..5fc8479 --- /dev/null +++ b/toontown/uberdog/DataStoreAIClient.py @@ -0,0 +1,140 @@ +from direct.directnotify.DirectNotifyGlobal import directNotify +from toontown.uberdog import DataStoreGlobals +from direct.showbase.DirectObject import DirectObject +import pickle + +class DataStoreAIClient(DirectObject): + """ + This class should be instantiated by any class that needs to + access an Uberdog data store. + + The client, as it is now, has the ability to create and destroy + DataStores on the Uberdog. This is mainly provided for backwards + compatibility with the Toontown architecture where the logic has + already been written for the AI side of things. + + For example, the HolidayManagerAI is something that could feasably + be run on the Uberdog, however it's already well established on + the AI. For this reason, we'll allow the HolidayManagerAI to + create and destroy data stores as it needs to. + + All it takes is one request to the Uberdog to carry out one of + these operations. Any further requests for data to an already + destroyed store will go unanswered. + + In the future, we should make attempts to keep the create/destroy + control on the Uberdog. That way, we have only one point of control + rather than several various AIs who may not be entirely in sync. + """ + + notify = directNotify.newCategory('DataStoreAIClient') + wantDsm = simbase.config.GetBool('want-ddsm', 1) + + def __init__(self,air,storeId,resultsCallback): + """ + storeId is a unique identifier to the type of store + the client wishes to connect to. There will only be + one store of this type on the Uberdog at any given time. + + resultsCallback is a function that accepts one argument, + the results returned from a query. The format of this + result argument is defined in the store's class definition. + """ + + if self.wantDsm: + self.__storeMgr = air.dataStoreManager + self.__storeId = storeId + self.__resultsCallback = resultsCallback + self.__storeClass = DataStoreGlobals.getStoreClass(storeId) + self.__queryTypesDict = self.__storeClass.QueryTypes + self.__queryStringDict = dict(list(zip(list(self.__queryTypesDict.values()), + list(self.__queryTypesDict.keys())))) + self.__enabled = False + + def openStore(self): + """ + Attempt to connect to the store defined by the storeId in the + __init__() function. If no store of this type is present on + the Uberdog, the store is created at this time. Queries can now + be sent to the store and replies from the store will be processed + by the client. + """ + if self.wantDsm: + self.__storeMgr.startStore(self.__storeId) + self.__startClient() + + def closeStore(self): + """ + This client will no longer receive results from the store. Also, + the store, if present on the Uberdog, will now be shutdown and all + data destroyed. Do not use this method unless you are sure that + the data is no longer needed by this, or any other, client. + """ + if self.wantDsm: + self.__stopClient() + self.__storeMgr.stopStore(self.__storeId) + + def isOpen(self): + return self.__enabled + + def getQueryTypes(self): + return list(self.__queryTypesDict.keys()) + + def getQueryTypeString(self,qId): + return self.__queryStringDict.get(qId,None) + + def sendQuery(self,queryTypeString,queryData): + """ + Sends a query to the data store. The format of the query is + defined in the store's class definition. + """ + if self.__enabled: + qId = self.__queryTypesDict.get(queryTypeString,None) + if qId is not None: + query = (qId,queryData) + # pack the data to be sent to the Uberdog store. + pQuery = pickle.dumps(query) + self.__storeMgr.queryStore(self.__storeId,pQuery) + else: + self.notify.debug('Tried to send invalid query type: \'%s\'' % (queryTypeString,)) + else: + self.notify.warning('Client currently stopped. \'%s\' query will fail.' % (queryTypeString,)) + + def receiveResults(self,data): + """ + Upon receiving a query, the store will respond with a result. + This function will call the resultsCallback function with the + result data as its sole argument. Try to treat the + resultsCallback function as an event that is fired whenever + the client receives data from the store. + """ + # unpack the results from the Uberdog store. + + if data == 'Store not found': + self.notify.debug('%s not present on uberdog. Query dropped.' %(self.__storeClass.__name__,)) + else: + results = pickle.loads(data) + self.__resultsCallback(results) + + def __startClient(self): + """ + Allow the client to send queries and receive results from its + associated data store. + """ + self.accept('TDS-results-%d'%self.__storeId,self.receiveResults) + self.__enabled = True + + def __stopClient(self): + """ + Disallow the client from sending queries and receiving results + from its associated data store. + """ + self.ignoreAll() + self.__enabled = False + + def deleteBackupStores(self): + """ + Delete any backed up stores from previous year's + """ + if self.wantDsm: + self.__storeMgr.deleteBackupStores()