AI: Implement Holidays

This uses anesidora and my python3 fixes from modern anesidora
This commit is contained in:
DarthNihilus1 2023-07-28 19:59:56 +04:00
parent 464c2d45f6
commit 7c22f939d9
47 changed files with 7348 additions and 34 deletions

View File

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

View File

@ -15,4 +15,4 @@ class BlackCatHolidayMgrAI(HolidayBaseAI.HolidayBaseAI):
bboard.post(BlackCatHolidayMgrAI.PostName)
def stop(self):
bboard.remove(BlackCatHolidayMgrAI.PostName)
bboard.remove(BlackCatHolidayMgrAI.PostName)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

264
toontown/ai/HolidayInfo.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
// For now, since we are not installing Python files, this file can
// remain empty.

View File

View File

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

View File

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

View File

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