AI: Implement Holidays
This uses anesidora and my python3 fixes from modern anesidora
This commit is contained in:
parent
464c2d45f6
commit
7c22f939d9
|
|
@ -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()
|
||||
|
|
@ -15,4 +15,4 @@ class BlackCatHolidayMgrAI(HolidayBaseAI.HolidayBaseAI):
|
|||
bboard.post(BlackCatHolidayMgrAI.PostName)
|
||||
|
||||
def stop(self):
|
||||
bboard.remove(BlackCatHolidayMgrAI.PostName)
|
||||
bboard.remove(BlackCatHolidayMgrAI.PostName)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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])
|
||||
|
||||
|
|
@ -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])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
@ -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])
|
||||
|
||||
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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...')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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])
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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])
|
||||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// For now, since we are not installing Python files, this file can
|
||||
// remain empty.
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue