Upload assets/fixed up-to-date DarthMDev code

This commit is contained in:
Sam Sneed 2024-07-14 15:28:28 -05:00
parent ebf43e020c
commit 5c3cd557c6
96 changed files with 10393 additions and 2975 deletions

View file

@ -0,0 +1,33 @@
##############################################
# Class: LowGravManagerAI
# This class handles April Fools changes
##############################################
from toontown.ai import HolidayBaseAI
from toontown.ai import CostumeManagerAI
from toontown.toonbase import ToontownGlobals
from direct.showbase import DirectObject
from toontown.toonbase import TTLocalizer
from direct.directnotify import DirectNotifyGlobal
class AprilFoolsManagerAI(CostumeManagerAI.CostumeManagerAI):
notify = DirectNotifyGlobal.directNotify.newCategory('AprilFoolsManagerAI')
def __init__(self, air, holidayId):
CostumeManagerAI.CostumeManagerAI.__init__(self, air, holidayId)
# Overridden function
def start(self):
CostumeManagerAI.CostumeManagerAI.start(self)
estateManager = simbase.air.doFind("EstateManagerAI.EstateManagerAI")
if estateManager != None:
estateManager.startAprilFools()
# Overridden function
def stop(self):
CostumeManagerAI.CostumeManagerAI.stop(self)
estateManager = simbase.air.doFind("EstateManagerAI.EstateManagerAI")
if estateManager != None:
estateManager.stopAprilFools()

View file

@ -0,0 +1,249 @@
##############################################
# Class: CostumeManagerAI
# This class handles the loading of new
# models that will replace models in one
# or more hoods based on the holiday
# requirements.
##############################################
from toontown.ai import HolidayBaseAI
from toontown.toonbase import ToontownGlobals
from direct.directnotify import DirectNotifyGlobal
from toontown.classicchars import *
from direct.task import Task
from direct.fsm import State
from toontown.hood import *
from direct.showbase import DirectObject
from toontown.toonbase import TTLocalizer
from toontown.classicchars import *
from toontown.classicchars import DistributedVampireMickeyAI, DistributedSuperGoofyAI, DistributedWesternPlutoAI
from toontown.classicchars import DistributedWitchMinnieAI, DistributedMinnieAI, DistributedPlutoAI
from toontown.hood import MMHoodDataAI, BRHoodDataAI
class CostumeManagerAI(HolidayBaseAI.HolidayBaseAI, DirectObject.DirectObject):
notify = DirectNotifyGlobal.directNotify.newCategory('CostumeManagerAI')
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
self.__classicChars = {}
self.hoods = []
self.runningState = 1
self.cCharsSwitched = 0
# For use with magic words
self.stopForever = False
# Overridden function
######################################################
# General format: if(self.holidayId == HOLIDAY_ID)
# Get hood and call switchChars with new hood
# and classicChar class.
######################################################
def start(self):
if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES):
self.accept("TTHoodSpawned", self.__welcomeValleySpawned)
self.accept("TTHoodDestroyed", self.__welcomeValleyDestroyed)
self.accept("GSHoodSpawned", self.__welcomeValleySpawned)
self.accept("GSHoodDestroyed", self.__welcomeValleyDestroyed)
if hasattr(simbase.air, "holidayManager") and simbase.air.holidayManager is not None:
if self.holidayId in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[self.holidayId] != None:
return
for hood in simbase.air.hoods:
if isinstance(hood, TTHoodDataAI.TTHoodDataAI):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, MMHoodDataAI.MMHoodDataAI):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, GSHoodDataAI.GSHoodDataAI):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, BRHoodDataAI.BRHoodDataAI):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES):
self.accept("TTHoodSpawned", self.__welcomeValleySpawned)
self.accept("TTHoodDestroyed", self.__welcomeValleyDestroyed)
self.accept("GSHoodSpawned", self.__welcomeValleySpawned)
self.accept("GSHoodDestroyed", self.__welcomeValleyDestroyed)
if hasattr(simbase.air, "holidayManager"):
if self.holidayId in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[self.holidayId] != None:
return
for hood in simbase.air.hoods:
# The character is neither transitioning or has transitioned into a different costume
if hasattr(hood, "classicChar") and hood.classicChar.transitionToCostume == 0 and hood.classicChar.diffPath == None:
if isinstance(hood, TTHoodDataAI.TTHoodDataAI):
# import pdb; pdb.set_trace()
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, BRHoodDataAI.BRHoodDataAI ):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, MMHoodDataAI.MMHoodDataAI ):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, DGHoodDataAI.DGHoodDataAI ):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, DLHoodDataAI.DLHoodDataAI ):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
elif isinstance(hood, GSHoodDataAI.GSHoodDataAI ):
self.hoods.append(hood)
self.__classicChars[str(hood)] = 1
hood.classicChar.transitionCostume()
# Overridden function
def stop(self):
self.ignoreAll()
del self.__classicChars
pass
def goingToStop(self, stopForever=False):
# import pdb; pdb.set_trace()
self.notify.debug("GoingToStop")
self.stopForever = stopForever
self.runningState = 0
if(self.holidayId in [ToontownGlobals.HALLOWEEN_COSTUMES, ToontownGlobals.APRIL_FOOLS_COSTUMES]):
self.ignore("TTHoodSpawned")
self.ignore("GSHoodSpawned")
for hood in self.hoods:
hood.classicChar.transitionCostume()
self.__classicChars[str(hood)] = 0
def getRunningState(self):
return self.runningState
########################################################
# Trigger the switching of the character
########################################################
def triggerSwitch(self, curWalkNode, curChar):
if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES):
for hood in self.hoods:
if hood.classicChar == curChar:
hood.classicChar.fadeAway()
if(curChar.getName() == TTLocalizer.VampireMickey):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMickeyAI.DistributedMickeyAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Mickey):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedVampireMickeyAI.DistributedVampireMickeyAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.WitchMinnie):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMinnieAI.DistributedMinnieAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Minnie):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedWitchMinnieAI.DistributedWitchMinnieAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Goofy):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedSuperGoofyAI.DistributedSuperGoofyAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.SuperGoofy):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedGoofySpeedwayAI.DistributedGoofySpeedwayAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Pluto):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedWesternPlutoAI.DistributedWesternPlutoAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.WesternPluto):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedPlutoAI.DistributedPlutoAI, curWalkNode, hood])
elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES):
for hood in self.hoods:
if hood.classicChar == curChar:
hood.classicChar.fadeAway()
if(curChar.getName() == TTLocalizer.Daisy):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMickeyAI.DistributedMickeyAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Mickey):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedDaisyAI.DistributedDaisyAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Goofy):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedDonaldAI.DistributedDonaldAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Donald):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedGoofySpeedwayAI.DistributedGoofySpeedwayAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Pluto):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedMinnieAI.DistributedMinnieAI, curWalkNode, hood])
elif(curChar.getName() == TTLocalizer.Minnie):
taskMgr.doMethodLater(0.5, self.__switchChars, "SwitchChars"+str(hood), extraArgs = [DistributedPlutoAI.DistributedPlutoAI, curWalkNode, hood])
########################################################
# Switched the classic character with a new one
# represented by class 'newChar' in 'hood'.
########################################################
def __switchChars(self, newChar, walkNode, hood):
self.notify.debug("SwitchingChars %s to %s" %(hood.classicChar, newChar))
self.notify.debugStateCall(self)
hood.classicChar.requestDelete()
if hasattr(hood, "air") and hood.air:
hood.classicChar = newChar(hood.air)
hood.classicChar.generateWithRequired(hood.zoneId)
hood.addDistObj(hood.classicChar)
hood.classicChar.walk.setCurNode(walkNode)
hood.classicChar.fsm.request('Walk')
else:
self.notify.warning("Hood empty during character switch")
holidayDone = 1
for classicChar in self.__classicChars.values():
if classicChar == 1:
holidayDone = 0
if holidayDone:
self.cCharsSwitched += 1
if self.cCharsSwitched == len(self.__classicChars):
simbase.air.holidayManager.delayedEnd(self.holidayId, self.stopForever)
########################################################
# Function to handle the spawning of a new welcome
# valley server
########################################################
def __welcomeValleySpawned(self, newHood):
if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES):
self.__addAVampire(newHood)
elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES):
self.__aprilFoolsSwap(newHood)
def __welcomeValleyDestroyed(self, newHood):
if(self.holidayId == ToontownGlobals.HALLOWEEN_COSTUMES):
self.__removeAVampire(newHood)
elif(self.holidayId == ToontownGlobals.APRIL_FOOLS_COSTUMES):
self.__aprilFoolsRevert(newHood)
def __aprilFoolsSwap(self, newHood):
for hood in self.hoods:
if hood == newHood:
return
self.hoods.append(newHood)
self.__classicChars[str(newHood)] = 1
newHood.classicChar.transitionCostume()
def __aprilFoolsRevert(self, deadHood):
if str(deadHood) in self.__classicChars:
del self.__classicChars[str(deadHood)]
for hood in self.hoods:
if hood == deadHood:
self.hoods.remove(hood)
return
def __addAVampire(self, newHood):
for hood in self.hoods:
if hood == newHood:
return
self.hoods.append(newHood)
self.__classicChars[str(newHood)] = 1
newHood.classicChar.transitionCostume()
def __removeAVampire(self, deadHood):
if str(deadHood) in self.__classicChars:
del self.__classicChars[str(deadHood)]
for hood in self.hoods:
if hood == deadHood:
self.hoods.remove(hood)
return

View file

@ -1,5 +1,35 @@
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.distributed import DistributedObjectAI
class DistributedBlackCatMgrAI(DistributedObjectAI): class DistributedBlackCatMgrAI(DistributedObjectAI.DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBlackCatMgrAI') """This object sits in the tutorial zone with Flippy and listens for
the avatar to say 'Toontastic!' when prompted to say something. At that
point, if the avatar is a cat, it gives them the 'black cat' DNA."""
notify = DirectNotifyGlobal.directNotify.newCategory(
'DistributedBlackCatMgrAI')
def __init__(self, air, avId):
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
self.avId = avId
def getAvId(self):
return self.avId
def doBlackCatTransformation(self):
avId = self.avId
if self.air.getAvatarIdFromSender() != avId:
self.air.writeServerEvent(
'suspicious', avId,
'%s: expected msg from %s, got msg from %s' % (
self.__class__.__name__, avId, self.air.getAvatarIdFromSender()))
return
av = self.air.doId2do.get(self.avId)
if not av:
DistributedBlackCatMgrAI.notify.warning(
'tried to turn av %s into a black cat, but they left' % avId)
else:
self.air.writeServerEvent('blackCatMade', avId, 'turning av %s into a black cat' % avId)
DistributedBlackCatMgrAI.notify.warning(
'turning av %s into a black cat' % avId)
av.makeBlackCat()

View file

@ -1,5 +1,147 @@
import datetime
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.distributed import DistributedObjectAI
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)
class DistributedPhaseEventMgrAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPhaseEventMgrAI')

View file

@ -1,5 +1,21 @@
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.distributed import DistributedObjectAI
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)
class DistributedScavengerHuntTargetAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedScavengerHuntTarget')

View file

@ -1,5 +1,22 @@
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.distributed import DistributedObjectAI
from . import DistributedScavengerHuntTargetAI
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)
class DistributedWinterCarolingTargetAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedWinterCarolingTargetAI')

View file

@ -4,6 +4,9 @@ from direct.task import Task
from toontown.effects import DistributedFireworkShowAI from toontown.effects import DistributedFireworkShowAI
class HolidayBaseAI: class HolidayBaseAI:
"""
Base class for all holidays
"""
def __init__(self, air, holidayId): def __init__(self, air, holidayId):
self.air = air self.air = air
@ -14,3 +17,6 @@ class HolidayBaseAI:
def stop(self): def stop(self):
pass pass

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

@ -0,0 +1,264 @@
#################################################################
# File: HolidayInfo.py
# Purpose: Coming Soon...
#################################################################
#################################################################
# Python Specific Modules
#################################################################
import random
import time
#################################################################
# Global Methods
#################################################################
#############################################################
# Method: cmpTime
# Purpose: This method is used when sorting a list based
# on two time tuples. For the HolidayInfo tuples,
# we would like the list of tuples to be ordered
# in a chronological sequence for easy of transition
# from one holiday date to the next.
# Input: None
# Output: returns the comparison value
#############################################################
def cmpDates(tuple1, tuple2):
numValues = len(tuple1)
for i in range(numValues):
if tuple1[i] > tuple2[i]:
return 1
elif tuple1[i] < tuple2[i]:
return -1
return 0
#################################################################
# Class: ModfiedIter
# Purpose: The python iterator only allows one to go forward
# and ends
# NOTE: Implementation for this will likely change so that it
# will handle removals from the sequence gracefully. This
# currently does not do so.
#################################################################
class ModifiedIter:
def __init__(self, seq):
self._seq = seq
self._idx = 0
self._storedIndex = 0
#############################################################
# Method: current
# Purpose: This method returns the current element that the
# iterator references.
# Input: None
# Output: returns the current element
#############################################################
def current(self):
try:
return self._seq[self._idx]
except IndexError:
raise StopIteration
#############################################################
# Method: next
# Purpose: This method emulates the python next method in that
# it updates the reference to the next element in
# the sequence. Unlike the python next method, it
# wraps around the sequence.
# Input: None
# Output: returns the new current element
#############################################################
def __next__(self):
try:
lastIdx = len(self._seq) - 1
self._idx = ((lastIdx == self._idx) and [0] or [self._idx+1])[0]
return self._seq[self._idx]
except IndexError:
raise StopIteration
#############################################################
# Method: prev
# Purpose: This method is similar to the python next method
# except that it updates the reference to the previous
# element in the sequence. This method wraps around
# around the sequence.
# Input: None
# Output: returns the new current element
#############################################################
def prev(self):
try:
lastIdx = len(self._seq) - 1
self._idx = ((self._idx == 0) and [lastIdx] or [self._idx-1])[0]
return self._seq[self._idx]
except IndexError:
raise StopIteration
#############################################################
# Method: peekNext
# Purpose: This method provides a look at functionality to
# see the next element in the list.
# Input: None
# Output: returns the next element
#############################################################
def peekNext(self):
try:
idx = self._idx
lastIdx = len(self._seq) - 1
idx = ((lastIdx == idx) and [0] or [idx+1])[0]
return self._seq[idx]
except:
raise StopIteration
#############################################################
# Method: peekPrev
# Purpose: This method provides a look at functionality to
# see the previous element in the list.
# Input: None
# Output: returns the next element
#############################################################
def peekPrev(self):
try:
idx = self._idx
lastIdx = len(self._seq) - 1
idx = ((idx == 0) and [lastIdx] or [idx-1])[0]
return self._seq[idx]
except IndexError:
raise StopIteration
#############################################################
# Method: setTo
# Purpose: This method sets the iterator to a known element
# Input: an element
# Output: true if element was found, false otherwise
#############################################################
def setTo(self, element):
try:
index = self._seq.index(element)
self._idx = index
return True
except ValueError:
return False
#############################################################
# Method: store
# Purpose: This method stores the iterator state
# Input: None
# Output: None
#############################################################
def store(self):
self._storedIndex = self._idx
#############################################################
# Method: store
# Purpose: This method restores the iterator state
# Input: None
# Output: None
#############################################################
def restore(self):
self._idx = self._storedIndex
#################################################################
# Class: HolidayInfo_Base
# Purpose: A Base Class for all derived.
#################################################################
class HolidayInfo_Base:
#############################################################
# Method: __init__
# Purpose: Provides initial construction of the HolidayInfo
# instance.
# Input: holidayClass - class type of the holiday, for
# instance - Fireworks.
# Output: None
#############################################################
def __init__(self, holidayClass, displayOnCalendar):
self.__holidayClass = holidayClass
self.tupleList = []
self.currElemIter = ModifiedIter(self.tupleList)
self.displayOnCalendar = displayOnCalendar
#############################################################
# Method: getClass
# Purpose: This method returns the class type of the
# Holiday.
# Input: None
# Output: returns Holiday Class Type
#############################################################
def getClass(self):
return self.__holidayClass
#############################################################
# Method: getStartTime
# Purpose: This method returns the current start time of
# the holiday.
# Input: date - the current date represented as a tuple
# Output: returns current start time
#############################################################
def getStartTime(self, date):
startTuple = self.currElemIter.current()[0]
return self.getTime(date, startTuple)
#############################################################
# Method: getEndTime
# Purpose: This method returns the current end time of
# the holiday.
# Input: date - the current date represented as a tuple
# Output: returns current end time
#############################################################
def getEndTime(self, date):
endTuple = self.currElemIter.current()[1]
return self.getTime(date, endTuple)
#############################################################
# Method: getDate
# Purpose: This method returns the current date in a known format
# Input: None
# Output: date represented as a tuple
#############################################################
def getDate(self):
localtime = time.localtime()
date = (localtime[0], # Year
localtime[1], # Month
localtime[2], # Day
localtime[6]) # WDay
return date
#############################################################
# Method: getTime
# Purpose: This method returns the time based on the supplied
# date and t.
# Input: date - the current date represented as a tuple
# t - the current time tuple
# Output: returns the time in secs based on date and t
#############################################################
def getTime(self, date, t):
return time.mktime((date[0],
date[1],
date[2],
t[0],
t[1],
t[2],
0,
0,
-1))
#############################################################
# Method: getNumHolidays
# Purpose: This method returns the number of dates on which
# the holiday will be played.
# Input: None
# Output: returns the number of dates
#############################################################
def getNumHolidays(self):
return len(self.tupleList)
def hasPhaseDates(self):
"""Returns true if the holiday ramps us over time to several different phases."""
return False
def getPhaseDates(self):
"""Used when the holiday ramps us over time to several different phases.
Returns None when the holiday does not use phases"""
return None

View file

@ -0,0 +1,106 @@
#################################################################
# File: HolidayInfoDaily.py
# Purpose: Contains the class implementation for daily Holidays.
#################################################################
#################################################################
# Python Specific Modules
#################################################################
from toontown.ai.HolidayInfo import *
#################################################################
# Python Specific Modules
#################################################################
import random
import time
#################################################################
# Class: HolidayInfo_Daily
# Purpose: This HolidayInfo Derived Class is used for holidays
# that occur on a daily basis. For instance, running
# a holiday every day at 9 am to 12 pm.
#################################################################
class HolidayInfo_Daily(HolidayInfo_Base):
#############################################################
# Method: __init__
# Purpose: Provides initial construction of the Daily Holiday
# Info object. It generates the list of times that
# the holiday should be run every day.
# Input: holidayClass - class type of the holiday, for
# instance - Fireworks.
# timeList - a list of tuples containing the start
# and end dates for this holiday.
# Output: None
#############################################################
def __init__(self, holidayClass, dateList, displayOnCalendar):
HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar)
dateElemIter = ModifiedIter(dateList)
for i in range(len(dateList)//2):
start = dateElemIter.current()
end = next(dateElemIter)
self.tupleList.append((start, end))
next(dateElemIter)
#############################################################
# Method: getNextHolidayTime
# Purpose: This method finds the next appropriate time to
# start this holiday. It searches through the list
# of time tuples, and performs the necessary
# computations for finding the time.
# Input: currTime - current time
# Output: returns the next start time of the holiday
#############################################################
def getNextHolidayTime(self, currTime):
localTime = time.localtime()
date = (localTime[0], # year
localTime[1], # month
localTime[2], # day
)
for i in range(len(self.tupleList)):
# Retrieve the Start/End Tuples for the next time
# the holiday should be scheduled.
startTuple, endTuple = self.currElemIter.peekNext()
# Retrieve the current Start Time and
# the next Start Time.
cStartTime = self.currElemIter.current()[0]
nStartTime = self.currElemIter.peekNext()[0]
# If the current Start Time is larger than the
# next, we have reached the end of the list so
# we must schedule the
if cStartTime > nStartTime:
sTime = self.getTime((date[0], date[1], date[2]+1,), startTuple)
eTime = self.getTime((date[0], date[1], date[2]+1,), endTuple)
else:
sTime = self.getTime(date, startTuple)
eTime = self.getTime(date, endTuple)
if startTuple > endTuple:
eTime = self.getTime((date[0], date[1], date[2]+1,), endTuple)
else:
eTime = self.getTime(date, endTuple)
# Iterate to the next time before we check validity
# of the time.
next(self.currElemIter)
if (currTime < eTime):
return sTime
# We are back to the original element, thus we should
# schedule it for the next day.
start = self.currElemIter.current()[0]
return self.getTime((date[0], date[1], date[2]+1,), start)
#############################################################
# Method: adjustDate
# Purpose: This method adjusts the current day by one. This
# is typically called when an end time is less than
# a start time.
# Input: date - the date that needs to be adjusted
# Output: None
#############################################################
def adjustDate(self, date):
return (date[0], date[1], date[2]+1, date[3])

View file

@ -0,0 +1,186 @@
#################################################################
# File: HolidayInfoMonthly.py
# Purpose: Coming Soon...
#################################################################
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.ai.HolidayInfo import *
#################################################################
# Python Specific Modules
#################################################################
import calendar
import random
import time
import functools
#################################################################
# Class: HolidayInfo_Monthly
# Purpose: Stores all relevant information regarding an event,
# such as the type of event.
#################################################################
class HolidayInfo_Monthly(HolidayInfo_Base):
#############################################################
# Method: __init__
# Purpose: Provides initial construction of the Monthly Holiday
# Info object. It generates the list of times that
# the holiday should be run every week.
# Input: holidayClass - class type of the holiday, for
# instance - Fireworks.
# dateDict - a dictionary containing the days
# and their corresponding time tuples.
# { 31: [((9, 0, 0), (12, 0, 0))] }
# Holiday starts at 9am PST and ends at
# 12pm PST on the 31st of Every Month.
# Output: None
############################################################
def __init__(self, holidayClass, dateList, displayOnCalendar):
HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar)
dateElemIter = ModifiedIter(dateList)
for i in range(len(dateList)//2):
start = dateElemIter.current()
end = next(dateElemIter)
finalTuple = self.__setTuplesMonthly(start, end)
self.tupleList.append(finalTuple)
next(dateElemIter)
self.tupleList.sort(key=functools.cmp_to_key(cmpDates))
#############################################################
# Method: __clampTimeTuples(self, sTuple, eTuple)
# Purpose: This method clamps any dates that go above the
# number of days in the particular month.
# For example, suppose the holiday ends on the 31st
# of every month. What about February? This clamps
# the holiday to the last day of February.
# Input: date - the current date represented as a tuple
# Output: returns the adjusted tuple pairing
#
# NOTE: This method also adjusts the start tuple day if
# the end date needs to be clamped. The adjustment is
# by the number of days that the end date is clamped.
# The reason for this is that it is assumed that the
# holiday should spand x number of days. We merely shift
# the days down if a clamping occurs.
#############################################################
def __clampTimeTuples(self, sTuple, eTuple):
year = time.localtime()[0]
month = time.localtime()[1]
wday, numDays = calendar.monthrange(year, month)
if (eTuple[0] > numDays) and (eTuple[0] > sTuple[0]):
dayOffset = (numDays - eTuple[0])
# This snippet of code emulates the C++ Ternary operator '?'
# Clamp the startTuple day to 1 if the offset takes it below 0.
day = ((sTuple[0]+dayOffset > 0) and [sTuple[0]+dayOffset] or [1])[0]
sTuple = (day, sTuple[1], sTuple[2], sTuple[3])
eTuple = (eTuple[0]+dayOffset, eTuple[1], eTuple[2], eTuple[3])
return (sTuple, eTuple)
# date comes in the form of date[year, month, day]
# only day is relevant.
#############################################################
# Method: getTime
# Purpose: This method returns the time. Overrides the base
# definiton of HolidayInfo.
# Input: date - the current date represented as a tuple
# Output: returns the time in secs based on date and t
#############################################################
def getTime(self, date, t):
# t is of the form (day, hour, min, sec)
# date is of the form (year, month, day, weekday)
return time.mktime((date[0], # year
date[1], # month
t[0], # day
t[1], # hour
t[2], # second
t[3], # minute
0,
0,
-1))
#############################################################
# Method: getNextHolidayTime
# Purpose: This method finds the next appropriate time to
# start this holiday. It searches through the list
# of time tuples, and performs the necessary
# computations for finding the time.
# Input: currTime - current time
# Output: returns the next start time of the holiday
#############################################################
def getNextHolidayTime(self, currTime):
currYear = time.localtime()[0]
sCurrMonth = time.localtime()[1]
eCurrMonth = sCurrMonth
for i in range(len(self.tupleList)):
sDay = self.currElemIter.current()[0][0]
nDay = self.currElemIter.peekNext()[0][0]
startTuple, endTuple = self.currElemIter.peekNext()
# If the end day is less than the start day, it is
# in the proceeding month. Adjust the end month accordingly.
# This assures that the proper time tuple will be chosen from
# the list.
if endTuple[0] < startTuple[0]:
eCurrMonth += 1
if sDay > nDay:
# Since the next day is less than the current day,
# then we have reached the end of the list and the next
# date should be in the proceeding month.
sTime = self.getTime((currYear, sCurrMonth+1,), startTuple)
eTime = self.getTime((currYear, eCurrMonth+1,), endTuple)
elif sDay == nDay:
# Since the next tuple is of the same day, we must check
# the time tuples to see if the next time is greater than
# the current. If it is, that means we have reached the end
# of the list and should go into the next month.
curr = self.currElemIter.current()[0]
time1 = (curr[1], curr[2], curr[3])
time2 = (startTuple[1], startTuple[2], startTuple[3])
if time1 > time2:
sTime = self.getTime((currYear, sCurrMonth+1,), startTuple)
eTime = self.getTime((currYear, eCurrMonth+1,), endTuple)
else:
sTime = self.getTime((currYear, sCurrMonth,), startTuple)
eTime = self.getTime((currYear, eCurrMonth,), endTuple)
else:
# We have not reached the end of the list, calculate times
# accordingly.
sTime = self.getTime((currYear, sCurrMonth,), startTuple)
eTime = self.getTime((currYear, eCurrMonth,), endTuple)
next(self.currElemIter)
if (currTime < eTime):
return sTime
# We are back to the original element, thus we should schedule it
# for the next month.
start = self.currElemIter.current()[0]
return self.getTime((currYear, currMonth+1,), start)
#############################################################
# Method: adjustDate
# Purpose: This method adjusts the current day by a month. This
# is typically called when an end time is less than
# a start time.
# Input: date - the date that needs to be adjusted
# Output: None
#############################################################
def adjustDate(self, date):
return (date[0], date[1]+1, date[2], date[3])

View file

@ -0,0 +1,169 @@
#################################################################
# File: HolidayInfoOncely.py
# Purpose: Coming Soon...
#################################################################
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.ai.HolidayInfo import *
#################################################################
# Python Specific Modules
#################################################################
import random
import time
import datetime
import functools
#################################################################
# Class: HolidayInfo_Oncely
# Purpose: Stores all relevant information regarding an event,
# such as the type of event.
#################################################################
class HolidayInfo_Oncely(HolidayInfo_Base):
#############################################################
# Method: __init__
# Purpose: Provides initial construction of the Oncely Holiday
# Info object. This type of holiday only happens once!
#
# Input: holidayClass - class type of the holiday, for
# instance - Fireworks.
# dateDict - a dictionary containing the Months,
# which each have a dictionary of days with
# their corresponding times.
# { Month.JULY: {31: [((9, 0, 0), (12, 0, 0))]} }
# Holiday starts at 9am PST and ends at
# 12pm PST on July 31st of Every Year.
# Output: None
############################################################
def __init__(self, holidayClass, dateList, displayOnCalendar, phaseDates = None, testHolidays = None):
"""Phase dates adds a way for a ramping up holiday to go to the next phase."""
# I briefly considered putting phase dates in the definition of the HolidayAI class
# but then that would put when it starts, and the different phase times in two
# separate files. This feels much safer.
# Implicit in this definition, if a holiday has 1 phase date, there are 2 phases
HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar)
dateElemIter = ModifiedIter(dateList)
for i in range(len(dateList)//2):
start = dateElemIter.current()
end = next(dateElemIter)
self.tupleList.append((start, end))
next(dateElemIter)
self.tupleList.sort(key=functools.cmp_to_key(cmpDates))
self.phaseDates = None
self.curPhase = 0
if phaseDates:
self.processPhaseDates(phaseDates)
self.testHolidays = testHolidays
#############################################################
# Method: getTime
# Purpose: This method returns the time. Overrides the base
# definiton of HolidayInfo.
# Input: date - the current date represented as a tuple
# Output: returns the time in secs based on date and t
#############################################################
def getTime(self, date, t):
# t is of the form (year, month, day, hour, min, sec)
# date is of the form (year, month, day, weekday) - not used in this class
return time.mktime((t[0], # year
t[1], # month
t[2], # day
t[3], # hour
t[4], # second
t[5], # minute
0,
0,
-1))
#############################################################
# Method: getNextHolidayTime
# Purpose: This type of holiday only happens once, so just return None
#
# Input: currTime - current time
# Output: returns the next start time of the holiday
#############################################################
def getNextHolidayTime(self, currTime):
"""
Purpose: This method finds the next appropriate time to
start this holiday. It searches through the list
of time tuples, and performs the necessary
computations for finding the time.
Input: currTime - current time
Output: returns the next start time of the holiday, could be None
"""
result = None
for i in range(len(self.tupleList)):
if i == 0:
# we need to setup currElem properly if we start
# in the middle of a oncely holiday with multiple starts
self.currElemIter.setTo(self.tupleList[0])
startTuple = self.tupleList[i][0]
endTuple = self.tupleList[i][1]
startNextTime = self.getTime(None, startTuple)
endNextTime = self.getTime(None, endTuple)
if startNextTime <= currTime and \
currTime <= endNextTime:
# we are between a start time and end time tuple
# start it now
result = currTime
break;
if currTime < startNextTime and \
currTime < endNextTime:
# we are waiting for the next pair of start,end times to arrive
result = startNextTime
break;
next(self.currElemIter)
return result
#############################################################
# Method: adjustDate
# Purpose: This method adjusts the current day by a year. This
# is typically called when an end time is less than
# a start time.
# Input: date - the date that needs to be adjusted
# Output: None
#############################################################
def adjustDate(self, date):
return (date[0]+1, date[1], date[2], date[3])
def processPhaseDates(self, phaseDates):
"""Convert the phase dates into datetimes."""
self.phaseDates = []
for curDate in phaseDates:
newTime = datetime.datetime(curDate[0], curDate[1], curDate[2], curDate[3], curDate[4], curDate[5])
self.phaseDates.append(newTime)
def getPhaseDates(self):
"""Returns our phase dates, should be None if not used."""
return self.phaseDates
def hasPhaseDates(self):
"""Returns False if we don't use phase dates."""
if self.phaseDates:
return True
else:
return False
#############################################################
# Run holiday in test mode
# Used to invoke other holidays for debugging purposes
#############################################################
def isTestHoliday(self):
""" Returns true if running the holiday in test mode """
if self.testHolidays:
return True
else:
return False
def getTestHolidays(self):
return self.testHolidays

View file

@ -0,0 +1,280 @@
#################################################################
# File: HolidayInfoRelatively.py
# Purpose: To have a means of specifying a holiday as
# the position of a specific weekday in a month.
# For instance Halloween is the 5th Friday of October.
#################################################################
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.ai.HolidayInfo import *
#################################################################
# Python Specific Modules
#################################################################
import calendar
import random
import time
from copy import deepcopy
import functools
from enum import IntEnum
Day = IntEnum("Day", ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', \
'FRIDAY', 'SATURDAY', 'SUNDAY'), start=0)
#################################################################
# Class: HolidayInfo_Relatively
# Purpose: Stores all relevant information regarding an event,
# such as the type of event.
#################################################################
class HolidayInfo_Relatively(HolidayInfo_Base):
#############################################################
# Method: __init__
# Purpose: Provides initial construction of the Monthly Holiday
# Info object. It generates the list of times that
# the holiday should be run every week.
# Input: holidayClass - class type of the holiday, for
# instance - Halloween.
# dateList - The date is specified as a pair of the
# following:
# [(Month.OCTOBER, 5, Day.FRIDAY, 10, 0, 0),
# (Month.OCTOBER, 5, Day.FRIDAY, 15, 0, 0) ]
# This means that the holiday is on the 5th Friday of October
# between 10am and 3pm.
# Output: None
############################################################
def __init__(self, holidayClass, dateList, displayOnCalendar):
HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar)
dateElemIter = ModifiedIter(dateList)
for i in range(len(dateList)//2):
start = dateElemIter.current()
end = next(dateElemIter)
self.tupleList.append((start, end))
next(dateElemIter)
self.tupleList.sort(key=functools.cmp_to_key(cmpDates))
self.weekDaysInMonth = [] # A matrix of the number of times a weekday repeats in a month
self.numDaysCorMatrix = [(28,0), (29, 1),
(30, 2), (31, 3)] # A matrix of the number of weekdays that repeat one extra
# time based on the number of days in the month. For instance
# in a month with 31 days, the first two week days occur
# one more time than the other days.
for i in range(7): # The minimum number of times a day repeats in a month
self.weekDaysInMonth.append((i,4))
############################################################
# Method: initRepMatrix
# Initialize the number of times weekdays get
# repeated in a month method.
############################################################
def initRepMatrix(self, year, month):
for i in range(7):
self.weekDaysInMonth[i] = (i,4)
startingWeekDay, numDays = calendar.monthrange(year, month)
for i in range(4):
if(numDays == self.numDaysCorMatrix[i][0]):
break
for j in range(self.numDaysCorMatrix[i][1]): # At this point we have a matrix of the weekdays and
self.weekDaysInMonth[startingWeekDay] = (self.weekDaysInMonth[startingWeekDay][0],
self.weekDaysInMonth[startingWeekDay][1]+1) # the number of times they repeat for the current month
startingWeekDay = (startingWeekDay+1)%7
#############################################################
# Method: getTime
# Purpose: This method returns the time. Overrides the base
# definiton of HolidayInfo.
# Input: date - the current date represented as a tuple
# Output: returns the time in secs based on date and t
#############################################################
def getTime(self, date, t):
# t is of the form (day, hour, min, sec)
# date is of the form (year, month, day, weekday)
repNum = t[1]
weekday = t[2]
self.initRepMatrix(date[0],t[0])
while(self.weekDaysInMonth[weekday][1] < repNum):
repNum -= 1
day = self.dayForWeekday(date[0], t[0], weekday, repNum)
return time.mktime((date[0], # year
t[0], # month
day, # day
t[3], # hour
t[4], # second
t[5], # minute
0,
0,
-1))
#############################################################
# Method: getStartTime
# Purpose: This method returns the current start time of
# the holiday.
# Input: date - the current date represented as a tuple
# Output: returns current start time
#############################################################
def getStartTime(self, date):
startTuple, endTuple = self.getUpdatedTuples(self.currElemIter.current())
return self.getTime(date, startTuple)
#############################################################
# Method: getEndTime
# Purpose: This method returns the current end time of
# the holiday.
# Input: date - the current date represented as a tuple
# Output: returns current end time
#############################################################
def getEndTime(self, date):
startTuple, endTuple = self.getUpdatedTuples(self.currElemIter.current())
return self.getTime(date, endTuple)
#############################################################
# Method: getNextHolidayTime
# Purpose: This method finds the next appropriate time to
# start this holiday. It searches through the list
# of time tuples, and performs the necessary
# computations for finding the time.
# Input: currTime - current time
# Output: returns the next start time of the holiday
#############################################################
def getNextHolidayTime(self, currTime):
sCurrYear = time.localtime()[0]
eCurrYear = sCurrYear
for i in range(len(self.tupleList)):
startTuple, endTuple = self.getUpdatedTuples(self.currElemIter.peekNext())
sMonth = startTuple[0]
nMonth = endTuple[0]
# If the end month is less than the start month, it is
# in the proceeding year. Adjust the end year accordingly.
# This assures that the proper time tuple will be chosen from
# the list.
if endTuple[0] < startTuple[0]:
eCurrYear += 1
if sMonth > nMonth:
# The holiday should be scheduled in the
# following year. There are two cases that
# may exist and they follow:
# Case 1: { May: [31], July: [(31)] }
# - Here, we end on July 31. The next
# time the holiday should start is on
# May 31, which would be in the following
# year.
# Case 2: { December: [(31, 1)], May: [31] }
# - Here, we end on January 1 of the new year,
# because this holiday spans from December
# to January. The next time the holiday should
# start is on May 31, which would be in the
# same year.
# Check to see if we are already in the next
# year due to overlapping holiday.
cMonth = time.localtime()[1]
if cMonth > nMonth:
# We have not crossed over into the new week
# as found in case 1.
sTime = self.getTime((sCurrYear+1,), startTuple)
eTime = self.getTime((eCurrYear+1,), endTuple)
else:
# We have already started the new year as found
# in case 2. Adjust time normally.
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
elif sMonth == nMonth:
if sDay > nDay:
# Since the next day is less than the current day,
# then we have reached the end of the list and the next
# date should be in the proceeding year.
sTime = self.getTime((sCurrYear+1,), startTuple)
eTime = self.getTime((eCurrYear+1,), endTuple)
elif sDay == nDay:
# Since the next tuple is of the same day, we must check
# the time tuples to see if the next time is greater than
# the current. If it is, that means we have reached the end
# of the list and should go into the next year..
curr = self.currElemIter.current()[0]
time1 = (curr[3], curr[4], curr[5])
time2 = (startTuple[3], startTuple[4], startTuple[5])
if time1 > time2:
sTime = self.getTime((sCurrYear+1,), startTuple)
eTime = self.getTime((eCurrYear+1,), endTuple)
else:
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
else:
# We have not reached the end of the list, calculate times
# accordingly.
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
else:
# We have not reached the end of the list, calculate times
# accordingly.
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
next(self.currElemIter)
if (currTime < eTime):
return sTime
# We are back to the original element, thus we should
# schedule it for the next year.
start = self.currElemIter.current()[0]
return self.getTime((sCurrYear+1,), start)
############################################################
# Method: getUpdatedTuples
# Returns the corrected pair of tuples based on
# the current month and year information
############################################################
def getUpdatedTuples(self, tuples):
sTuple = list(deepcopy(tuples[0]))
eTuple = list(deepcopy(tuples[1]))
sRepNum = sTuple[1]
sWeekday = sTuple[2]
eWeekday = eTuple[2]
while(1):
eRepNum = eTuple[1]
self.initRepMatrix(time.localtime()[0], sTuple[0])
while(self.weekDaysInMonth[sWeekday][1] < sRepNum):
sRepNum -= 1
sDay = self.dayForWeekday(time.localtime()[0], sTuple[0], sWeekday, sRepNum)
self.initRepMatrix(time.localtime()[0], eTuple[0])
while(self.weekDaysInMonth[eWeekday][1] < eRepNum):
eRepNum -= 1
nDay = self.dayForWeekday(time.localtime()[0], eTuple[0], eWeekday, eRepNum)
if(((nDay>sDay) and (eTuple[0] == sTuple[0]) \
and ((eTuple[1] - sTuple[1]) <= (nDay-sDay+abs(eWeekday-sWeekday))/7)) \
or (eTuple[0] != sTuple[0])):
break
# Handles the case when the end weekday is less than the start
if(self.weekDaysInMonth[eWeekday][1] > eRepNum):
eRepNum += 1
else:
eTuple[0] += 1
eTuple[1] = 1
return sTuple, eTuple
############################################################
# Method: dayForWeekday(month, weekday, repNum)
# Returns the day for a given weekday that has repeated
# repNum times for that month
############################################################
def dayForWeekday(self, year, month, weekday, repNum):
monthDays = calendar.monthcalendar(year, month)
if(monthDays[0][weekday] == 0):
repNum += 1
return monthDays[(repNum-1)][weekday]

View file

@ -0,0 +1,205 @@
#################################################################
# File: HolidayInfoWeekly.py
# Purpose: Coming Soon...
#################################################################
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.ai.HolidayInfo import *
#################################################################
# Python Specific Modules
#################################################################
import random
import time
import functools
#################################################################
# Class: HolidayInfo_Weekly
# Purpose: Stores all relevant information regarding a holiday.
# Note: Monday is designated as the first day of the week.
#################################################################
class HolidayInfo_Weekly(HolidayInfo_Base):
#############################################################
# Method: __init__
# Purpose: Provides initial construction of the Weekly Holiday
# Info object. It generates the list of times that
# the holiday should be run every week.
# Input: holidayClass - class type of the holiday, for
# instance - Fireworks.
# dateDict - a dictionary containing the weekdays
# and their corresponding time tuples.
# { Day.Monday: [((9, 0, 0), (12, 0, 0))] }
# Holiday starts at 9am PST and ends at
# 12pm PST every Monday.
# Output: None
#############################################################
def __init__(self, holidayClass, dateList, displayOnCalendar):
HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar)
dateElemIter = ModifiedIter(dateList)
for i in range(len(dateList)//2):
start = dateElemIter.current()
end = next(dateElemIter)
self.tupleList.append((start, end))
next(dateElemIter)
self.tupleList.sort(key=functools.cmp_to_key(cmpDates))
#############################################################
# Method: getStartTime
# Purpose: This method returns the current start time of
# the holiday. Overrides the base definiton of
# HolidayInfo.
# Input: date - the current date represented as a tuple
# Output: returns current start time
#############################################################
def getStartTime(self, date):
startTuple = self.currElemIter.current()[0]
return self.getTime(date, startTuple, True)
#############################################################
# Method: getEndTime
# Purpose: This method returns the current end time of
# the holiday. Overrides the base definiton of
# HolidayInfo.
# Input: date - the current date represented as a tuple
# Output: returns current end time
#############################################################
def getEndTime(self, date):
endTuple = self.currElemIter.current()[1]
return self.getTime(date, endTuple, False)
#############################################################
# Method: getTime
# Purpose: This method returns the time. Overrides the base
# definiton of HolidayInfo.
# Input: date - the current date represented as a tuple
# t - the current time tuple
# isStart - True if we want starting time,
# False if we want end time.
# isNextWeek - True if time should be computed for
# the next week, false if it should be
# computed for this week.
# Output: returns the time in secs based on date and t
#############################################################
def getTime(self, date, t, isStart = True, isNextWeek=False):
#print "Getting time for date = %s and t = %s" % (date, t)
cWDay = date[3]
sWDay = t[0]
dayOffset = sWDay - cWDay
if isNextWeek:
dayOffset += 7
day = date[2] + dayOffset
actualTime = time.mktime((date[0], date[1], day,
t[1], t[2], t[3],
0, 0, -1))
#print time.ctime(actualTime)
return actualTime
#############################################################
# Method: getNextHolidayTime
# Purpose: This method finds the next appropriate time to
# start this holiday. It searches through the list
# of time tuples, and performs the necessary
# computations for finding the time.
# Input: currTime - current time
# Output: returns the next start time of the holiday
#############################################################
def getNextHolidayTime(self, currTime):
date = self.getDate()
foundTime = None
# look through the list of holidays for the first one we haven't hit
# (don't attempt to increment the week)
for start, end in self.tupleList:
nextStartTime = self.getTime(date, start, True, False)
nextEndTime = self.getTime(date, end, False, False)
# We add a one minute fudge factor to prevent the current
# holiday from restarting if its endHoliday doLater fires early.
# This has the side effect that if the AI starts within one minute
# of a holiday ending, it will NOT start the holiday.
if currTime + 59 < nextEndTime:
foundTime = nextStartTime
break
if not foundTime:
# we have already passed the start time for all of these,
# add one week to the first one and use that
start, end = self.tupleList[0]
date = self.adjustDate(date)
foundTime = self.getTime(date, start, True, False)
self.currElemIter.setTo((start, end))
return foundTime
"""
for i in xrange(len((self.tupleList))):
# Retrieve Starting WDay for the current Element
# and the next element in the sequence.
sWDay = self.currElemIter.current()[0][0]
nWDay = self.currElemIter.peekNext()[0][0]
if sWDay > nWDay:
# The next date is in the following week. There
# are two cases that can exist and they follow:
# Case 1: [(1, 2), 3, 5]
# - Here, we have ended on a
# Saturday(5). The next time the holiday
# should fire up is on a Tuesday(1) of the
# next week.
# Case 2: [(6, 1), 3]
# - Here, we have ended on a Tuesday(1). The
# next time the holiday should fire will be
# on the Thursday(3) of the same week.
# Check to see if we are already in the next
# week due to overlapping holiday.
cWDay = date[3]
startTuple, endTuple = self.currElemIter.next()
if cWDay > nWDay:
# We have not started the new week as found
# in case 1.
sTime = self.getTime(date, startTuple, True, False)
else:
# We have already started the new week as found
# in case 2. Adjust time normally.
sTime = self.getTime(date, startTuple, True)
else:
startTuple, endTuple = self.currElemIter.next()
sTime = self.getTime(date, startTuple, True)
# Perform Check
if (currTime < sTime):
# Found next holiday day
return sTime
# This means that we arrived back to the original
# starting place. Update date and find time for
# next starting of holiday.
date = (date[0], date[1], date[2]+7, date[3])
startTuple = self.currElemIter.current()[0]
sTime = self.getTime(date, startTuple, True, True)
return sTime
"""
#############################################################
# Method: adjustDate
# Purpose: This method adjusts the current day by a week. This
# is typically called when an end time is less than
# a start time.
# Input: date - the date that needs to be adjusted
# Output: None
#############################################################
def adjustDate(self, date):
return (date[0], date[1], date[2]+7, date[3])

View file

@ -0,0 +1,174 @@
"""
# File: HolidayInfoYearly.py
# Purpose: Coming Soon...
"""
# Toontown Specific Modules
from toontown.ai.HolidayInfo import *
# Python Specific Modules
import random
import time
import functools
class HolidayInfo_Yearly(HolidayInfo_Base):
"""
Stores all relevant information regarding an event,
such as the type of event.
"""
def __init__(self, holidayClass, dateList, displayOnCalendar):
"""
Purpose: Provides initial construction of the Monthly Holiday
Info object. It generates the list of times that
the holiday should be run every week.
Input: holidayClass - class type of the holiday, for
instance - Fireworks.
dateDict - a dictionary containing the Months,
which each have a dictionary of days with
their corresponding times.
{ Month.JULY: {31: [((9, 0, 0), (12, 0, 0))]} }
Holiday starts at 9am PST and ends at
12pm PST on July 31st of Every Year.
Output: None
"""
HolidayInfo_Base.__init__(self, holidayClass, displayOnCalendar)
dateElemIter = ModifiedIter(dateList)
for i in range(len(dateList)//2):
start = dateElemIter.current()
end = next(dateElemIter)
self.tupleList.append((start, end))
next(dateElemIter)
self.tupleList.sort(key=functools.cmp_to_key(cmpDates))
def getTime(self, date, t):
"""
Purpose: This method returns the time. Overrides the base
definiton of HolidayInfo.
Input: date - the current date represented as a tuple
Output: returns the time in secs based on date and t
"""
# t is of the form (month, day, hour, min, sec)
# date is of the form (year, month, day, weekday)
return time.mktime((date[0], # year
t[0], # month
t[1], # day
t[2], # hour
t[3], # second
t[4], # minute
0,
0,
-1))
def getNextHolidayTime(self, currTime):
"""
Purpose: This method finds the next appropriate time to
start this holiday. It searches through the list
of time tuples, and performs the necessary
computations for finding the time.
Input: currTime - current time
Output: returns the next start time of the holiday
"""
sCurrYear = time.localtime()[0]
eCurrYear = sCurrYear
for i in range(len(self.tupleList)):
sMonth = self.currElemIter.current()[0][0]
nMonth = self.currElemIter.peekNext()[0][0]
startTuple, endTuple = self.currElemIter.peekNext()
# If the end month is less than the start month, it is
# in the proceeding year. Adjust the end year accordingly.
# This assures that the proper time tuple will be chosen from
# the list.
if endTuple[0] < startTuple[0]:
eCurrYear += 1
if sMonth > nMonth:
# The holiday should be scheduled in the
# following year. There are two cases that
# may exist and they follow:
# Case 1: { May: [31], July: [(31)] }
# - Here, we end on July 31. The next
# time the holiday should start is on
# May 31, which would be in the following
# year.
# Case 2: { December: [(31, 1)], May: [31] }
# - Here, we end on January 1 of the new year,
# because this holiday spans from December
# to January. The next time the holiday should
# start is on May 31, which would be in the
# same year.
# Check to see if we are already in the next
# year due to overlapping holiday.
cMonth = time.localtime()[0]
if cMonth > nMonth:
# We have not crossed over into the new week
# as found in case 1.
sTime = self.getTime((sCurrYear+1,), startTuple)
eTime = self.getTime((eCurrYear+1,), endTuple)
else:
# We have already started the new year as found
# in case 2. Adjust time normally.
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
elif sMonth == nMonth:
sDay = self.currElemIter.current()[0][1]
nDay = self.currElemIter.peekNext()[0][1]
if sDay > nDay:
# Since the next day is less than the current day,
# then we have reached the end of the list and the next
# date should be in the proceeding year.
sTime = self.getTime((sCurrYear+1,), startTuple)
eTime = self.getTime((eCurrYear+1,), endTuple)
elif sDay == nDay:
# Since the next tuple is of the same day, we must check
# the time tuples to see if the next time is greater than
# the current. If it is, that means we have reached the end
# of the list and should go into the next year..
curr = self.currElemIter.current()[0]
time1 = (curr[2], curr[3], curr[4])
time2 = (startTuple[2], startTuple[3], startTuple[4])
if time1 > time2:
sTime = self.getTime((sCurrYear+1,), startTuple)
eTime = self.getTime((eCurrYear+1,), endTuple)
else:
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
else:
# We have not reached the end of the list, calculate times
# accordingly.
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
else:
# We have not reached the end of the list, calculate times
# accordingly.
sTime = self.getTime((sCurrYear,), startTuple)
eTime = self.getTime((eCurrYear,), endTuple)
next(self.currElemIter)
if (currTime < eTime):
return sTime
# We are back to the original element, thus we should
# schedule it for the next year.
start = self.currElemIter.current()[0]
return self.getTime((sCurrYear+1,), start)
def adjustDate(self, date):
"""
Purpose: This method adjusts the current day by a year. This
is typically called when an end time is less than
a start time.
Input: date - the date that needs to be adjusted
Output: None
"""
return (date[0]+1, date[1], date[2], date[3])

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,92 @@
"""
The holidayRepeaterAI class repeats an existing holiday
over an infinite period of time
"""
from toontown.toonbase import ToontownGlobals
from direct.directnotify import DirectNotifyGlobal
from . import HolidayBaseAI
from toontown.spellbook import ToontownMagicWordManagerAI
scaleFactor = 12
restartWaitTime = 60
aiInitTime = 2.5
class HolidayRepeaterAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory('HolidayRepeaterAI')
def __init__(self, air, holidayId, startAndEndTuple, testHolidays):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
self.testHolidays = testHolidays
self.testHolidayStates = {}
self.aiInitialized = 0
def start(self):
"""
Immediately start running the test holidays on a loop
"""
if not hasattr(self.air, "holidayManager"):
taskMgr.doMethodLater(restartWaitTime, self.startLoop, "WaitForAir")
self.notify.warning("holidayManager not yet created")
return
elif self.aiInitialized == 0:
taskMgr.doMethodLater(aiInitTime, self.startLoop, "WaitForAir")
self.aiInitialized = 1
for holiday in list(self.testHolidays.keys()):
# Set the holiday state to show that it has not yet begun
if self.air.holidayManager.isHolidayRunning(holiday):
self.air.holidayManager.endHoliday(holiday, True)
self.testHolidayStates[holiday] = -1
nextStepIn = self.testHolidays[holiday][0]
taskMgr.doMethodLater(nextStepIn*scaleFactor, self.handleNewHolidayState, "testHoliday_" + str(holiday), extraArgs=[holiday])
def startLoop(self, task):
self.start()
return task.done
def handleNewHolidayState(self, holiday):
nextStepIn = -1
self.testHolidayStates[holiday] = self.testHolidayStates[holiday] + 1
curState = self.testHolidayStates[holiday]
if curState == 0:
if self.air.holidayManager.isHolidayRunning(holiday):
self.air.holidayManager.endHoliday(holiday, True)
self.notify.debug("Starting holiday: %s" %holiday)
simbase.air.newsManager.sendSystemMessage("Holiday " + str(holiday) + " started")
nextStepIn = self.testHolidays[holiday][(curState+1)] - self.testHolidays[holiday][curState]
self.air.holidayManager.startHoliday(holiday, testMode = 1)
elif len(self.testHolidays[holiday]) == (curState+1):
self.notify.debug("Ending holiday: %s" %holiday)
simbase.air.newsManager.sendSystemMessage("Holiday " + str(holiday) + " ended")
self.air.holidayManager.endHoliday(holiday, True)
self.testHolidayStates[holiday] = -1
else:
self.notify.debug("Forcing holiday: %s to phase: %s" %(holiday, curState))
simbase.air.newsManager.sendSystemMessage("Holiday " + str(holiday) + " is in phase "+str(curState))
nextStepIn = self.testHolidays[holiday][(curState+1)] - self.testHolidays[holiday][curState]
self.air.holidayManager.forcePhase(holiday, curState)
if nextStepIn == -1:
count = 0
for holiday in list(self.testHolidayStates.keys()):
if self.testHolidayStates[holiday] == -1:
count = count+1
if count == len(self.testHolidays):
self.notify.debug("Finished hoiday cycle")
simbase.air.newsManager.sendSystemMessage("Holiday cycle complete: "+ str(self.holidayId))
taskMgr.doMethodLater(restartWaitTime, self.startLoop, "RepeatHolidays")
else:
taskMgr.doMethodLater(nextStepIn*scaleFactor, self.handleNewHolidayState, "testHoliday_" + str(holiday), extraArgs=[holiday])
def stop(self):
"""
End all the Test holidays
"""
for holiday in list(self.testHolidays.keys()):
if taskMgr.hasTaskNamed("testHoliday_" + str(holiday)):
taskMgr.remove("testHoliday_" + str(holiday))
self.air.holidayManager.endHoliday(holiday, True)

View file

@ -0,0 +1,16 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PropBuffHolidayAI
from toontown.ai import DistributedPhaseEventMgrAI
from toontown.toonbase import ToontownGlobals
class HydrantBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'HydrantBuffHolidayAI')
PostName = 'HydrantBuffHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)

View file

@ -0,0 +1,47 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PhasedHolidayAI
from toontown.ai import DistributedHydrantZeroMgrAI
from toontown.toonbase import ToontownGlobals
class HydrantZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'HydrantZeroHolidayAI')
PostName = 'hydrantZeroHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
def start(self):
# instantiate the object
PhasedHolidayAI.PhasedHolidayAI.start(self)
self.hydrantZeroMgr = DistributedHydrantZeroMgrAI.DistributedHydrantZeroMgrAI (
self.air, self.startAndEndTimes, self.phaseDates)
self.hydrantZeroMgr.generateWithRequired(ToontownGlobals.UberZone)
# let the holiday system know we started
bboard.post(self.PostName)
def stop(self):
# let the holiday system know we stopped
bboard.remove(self.PostName)
# remove the object
#self.resistanceEmoteMgr.requestDelete()
self.hydrantZeroMgr.requestDelete()
def forcePhase(self, newPhase):
"""Force our holiday to a certain phase. Returns true if succesful"""
result = False
try:
newPhase = int(newPhase)
except:
newPhase = 0
if newPhase >= self.hydrantZeroMgr.getNumPhases():
self.notify.warning("newPhase %d invalid in forcePhase" % newPhase)
return
self.curPhase = newPhase
self.hydrantZeroMgr.forcePhase(newPhase)
result = True
return result

View file

@ -0,0 +1,16 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PropBuffHolidayAI
from toontown.ai import DistributedPhaseEventMgrAI
from toontown.toonbase import ToontownGlobals
class MailboxBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'MailboxBuffHolidayAI')
PostName = 'MailboxBuffHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)

View file

@ -0,0 +1,46 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PhasedHolidayAI
from toontown.ai import DistributedMailboxZeroMgrAI
from toontown.toonbase import ToontownGlobals
class MailboxZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'MailboxZeroHolidayAI')
PostName = 'mailboxZeroHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
def start(self):
# instantiate the object
PhasedHolidayAI.PhasedHolidayAI.start(self)
self.mailboxZeroMgr = DistributedMailboxZeroMgrAI.DistributedMailboxZeroMgrAI (
self.air, self.startAndEndTimes, self.phaseDates)
self.mailboxZeroMgr.generateWithRequired(ToontownGlobals.UberZone)
# let the holiday system know we started
bboard.post(self.PostName)
def stop(self):
# let the holiday system know we stopped
bboard.remove(self.PostName)
# remove the object
#self.resistanceEmoteMgr.requestDelete()
self.mailboxZeroMgr.requestDelete()
def forcePhase(self, newPhase):
"""Force our holiday to a certain phase. Returns true if succesful"""
result = False
try:
newPhase = int(newPhase)
except:
newPhase = 0
if newPhase >= self.mailboxZeroMgr.getNumPhases():
self.notify.warning("newPhase %d invalid in forcePhase" % newPhase)
return
self.curPhase = newPhase
self.mailboxZeroMgr.forcePhase(newPhase)
result = True
return result

View file

@ -1,20 +1,170 @@
from otp.ai.AIBaseGlobal import *
from pandac.PandaModules import *
from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI from toontown.toonbase import ToontownGlobals
class NewsManagerAI(DistributedObjectAI): class NewsManagerAI(DistributedObjectAI.DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('NewsManagerAI') 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): 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): 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): def getOncelyCalendarHolidays(self):
return [] """Return our list of oncely calendar holidays."""
return self.oncelyCalendarHolidays
def getRelativelyCalendarHolidays(self): def addMultipleStartHoliday(self, holidayId, startAndEndList):
return [] """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): def getMultipleStartHolidays(self):
return [] """Return our list of multiple start holidays."""
return self.multipleStartHolidays
def sendMultipleStartHolidays(self):
"""Force a send of the oncely calendar holidays."""
self.sendUpdate("setMultipleStartHolidays", [self.multipleStartHolidays])
def sendOncelyCalendarHolidays(self):
"""Force a send of the oncely calendar holidays."""
self.sendUpdate("setOncelyCalendarHolidays", [self.oncelyCalendarHolidays])
def addRelativelyCalendarHoliday(self, holidayId, firstStartTime, lastEndTime):
"""Add a new oncely holiday."""
# Note the holiday can have breaks in it. e.g. no bloodsucker invasion
# happens between 3 and 6 pm on halloween, however for simplicity
# we just note the first time it will happen, and the last end time for it
self.relativelyCalendarHolidays.append((holidayId, firstStartTime, lastEndTime))
def getRelativelyCalendarHolidays(self):
"""Return our list of Relatively calendar holidays."""
return self.relativelyCalendarHolidays
def sendRelativelyCalendarHolidays(self):
"""Force a send of the Relatively calendar holidays."""
self.sendUpdate("setRelativelyCalendarHolidays", [self.relativelyCalendarHolidays])

View file

@ -0,0 +1,140 @@
import datetime
import time
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.toonbase import ToontownGlobals
from toontown.ai import DistributedResistanceEmoteMgrAI
class StartAndEndTime:
def __init__(self, startTime, endTime):
"""Keep track of start and end datetimes, in a struct."""
self.start = startTime
self.end = endTime
def isInBetween(self, phaseTime):
"""Returns true if phaseTime is in between start and end times.
Note that it returns false if it is equal."""
result = False
if self.start < phaseTime and phaseTime < self.end:
result = True
return result
def isInBetweenInclusive(self, phaseTime):
"""Returns true if phaseTime is in between start and end times.
Note that it returns true if it is equal."""
result = False
if self.start <= phaseTime and phaseTime <= self.end:
result = True
return True
def __repr__(self):
return self.__str__()
def __str__(self):
return ("(%s %s)" % (self.start, self.end))
class PhasedHolidayAI(HolidayBaseAI.HolidayBaseAI):
"""This is the base class for holidays which have different phases.
They ramp up over time, such as hydrant zero, silly meter.
While each phase could have been implemented as a different holiday,
this makes sure the start and end times for each phase match up."""
# WARNING phased holidays with multiple start and end times have not been fully tested
# RAU coded with it in mind, but edge cases can easily slip through
notify = DirectNotifyGlobal.directNotify.newCategory(
'PhasedHolidayAI')
def __init__(self, air, holidayId, startAndEndTupleList, phaseDates):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
self.oldPhase = 0
self.curPhase = 0
self.phaseDates = phaseDates
self.startAndEndTimes = self.convertStartAndEndListToDateTimes(startAndEndTupleList)
self.sanityCheckPhaseDates()
# we assume phase 0 is before phaseDates[0],
# phase 1 is between phaseDates[0], phaseDates[1]
# the last phase is after phaseDates[-1]
# TODO should we have the FSM here or in the child class?
# Lets leave it in the child class in case it's really simple
def convertStartAndEndListToDateTimes(self, startAndEndTupleList):
"""Convert start and end times to a more manageable class."""
result = []
for startAndEnd in startAndEndTupleList:
startInfo = startAndEnd[0]
startTime = datetime.datetime(startInfo[0], startInfo[1], startInfo[2],
startInfo[3], startInfo[4], startInfo[5])
endInfo = startAndEnd[1]
endTime = datetime.datetime(endInfo[0], endInfo[1], endInfo[2],
endInfo[3], endInfo[4], endInfo[5])
result.append(StartAndEndTime(startTime, endTime))
return result
def sanityCheckPhaseDates(self):
"""Do some sanity checking on our phase dates."""
#Check phase dates are between end and start times."""
for phaseDate in self.phaseDates:
foundInBetween = False
for startAndEnd in self.startAndEndTimes:
if startAndEnd.isInBetween(phaseDate):
foundInBetween = True
break
if not foundInBetween:
self.notify.error("holiday %d, phaseDate=%s not in between start and end times" %
(self.holidayId, phaseDate))
# check the phase dates are ascending
for index in range( len(self.phaseDates) -1):
if not (self.phaseDates[index] < self.phaseDates[index +1]):
self.notify.error("phaseDate=%s coming before phaseDate=%s" %
(self.phaseDates[index], self.phaseDates[index+1]))
def calcPhase(self, myTime):
"""Return which phase we should be given parameter time.
Will return 0 if it's way before the start time,
and return the last phase if its way after the end time
"""
result = self.getNumPhases()
for index, phaseDate in enumerate( self.phaseDates):
if myTime < phaseDate:
result = index
break
return result
def getNumPhases(self):
"""Return how many phases we have."""
result = len(self.phaseDates) + 1
return result
def isValidStart(self, curStartTime):
"""Print out a message if we're starting at a valid time.
We could start early or later if it's forced by magic words."""
result = False
for startAndEnd in self.startAndEndTimes:
if startAndEnd.isInBetweenInclusive(curStartTime):
result = True
break
def start(self):
"""Start the holiday and set us to the correct phase."""
# start the holiday
# this equivalent to the same bit of code we use in HolidayManagerAI.createHolidays
curTime = datetime.datetime.today()
isValidStart = self.isValidStart(curTime)
if not isValidStart:
self.notify.warning("starting holiday %d at %s but self.startAndEndTimes= %s " %
(self.holidayId, curTime, self.startAndEndTimes))
pass
def stop(self):
pass
def forcePhase(self, newPhase):
self.notify.warning("Child class must defined forcePhase")

View file

@ -0,0 +1,31 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.toonbase import ToontownGlobals
from toontown.ai import DistributedPolarPlaceEffectMgrAI
EVENT_ZONE = 3821 # 'Hibernation Vacations' interior
class PolarPlaceEventMgrAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'PolarPlaceEventMgrAI')
PostName = 'polarPlaceEvent'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
self.polarPlaceEmoteMgr = None
def start(self):
# instantiate the object
self.polarPlaceEmoteMgr = DistributedPolarPlaceEffectMgrAI.DistributedPolarPlaceEffectMgrAI(
self.air)
self.polarPlaceEmoteMgr.generateWithRequired(EVENT_ZONE)
# let the holiday system know we started
bboard.post(PolarPlaceEventMgrAI.PostName)
def stop(self):
# let the holiday system know we stopped
bboard.remove(PolarPlaceEventMgrAI.PostName)
# remove the object
self.polarPlaceEmoteMgr.requestDelete()

View file

@ -0,0 +1,54 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PhasedHolidayAI
from toontown.ai import DistributedPhaseEventMgrAI
from toontown.toonbase import ToontownGlobals
class PropBuffHolidayAI(PhasedHolidayAI.PhasedHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'PropBuffHolidayAI')
# PostName = 'propBuffHoliday' # deliberately not set so child classes forced to define this
# and we avoid a conflict of say the laff buf holiday stomping on the drop buff holiday
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
def start(self):
# instantiate the object
PhasedHolidayAI.PhasedHolidayAI.start(self)
self.propBuffMgr = DistributedPhaseEventMgrAI.DistributedPhaseEventMgrAI (
self.air, self.startAndEndTimes, self.phaseDates)
self.propBuffMgr.generateWithRequired(ToontownGlobals.UberZone)
# let the holiday system know we started
bboard.post(self.PostName)
def stop(self):
# let the holiday system know we stopped
bboard.remove(self.PostName)
# remove the object
#self.resistanceEmoteMgr.requestDelete()
self.propBuffMgr.requestDelete()
def forcePhase(self, newPhase):
"""Force our holiday to a certain phase. Returns true if succesful"""
result = False
try:
newPhase = int(newPhase)
except:
newPhase = 0
if newPhase >= self.propBuffMgr.getNumPhases():
self.notify.warning("newPhase %d invalid in forcePhase" % newPhase)
return
self.curPhase = newPhase
self.propBuffMgr.forcePhase(newPhase)
result = True
return result
def getCurPhase(self):
"""Returns the buffMgr's current phase, may return -1."""
result = -1
if hasattr(self,"propBuffMgr"):
result = self.propBuffMgr.getCurPhase()
return result

View file

@ -0,0 +1,31 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.toonbase import ToontownGlobals
from toontown.ai import DistributedResistanceEmoteMgrAI
EVENT_ZONE = 9720 # 'Talking in Your Sleep Voiceover Training' interior
class ResistanceEventMgrAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'ResistanceEventMgrAI')
PostName = 'resistanceEvent'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
self.resistanceEmoteMgr = None
def start(self):
# instantiate the object
self.resistanceEmoteMgr = DistributedResistanceEmoteMgrAI.DistributedResistanceEmoteMgrAI(
self.air)
self.resistanceEmoteMgr.generateWithRequired(EVENT_ZONE)
# let the holiday system know we started
bboard.post(ResistanceEventMgrAI.PostName)
def stop(self):
# let the holiday system know we stopped
bboard.remove(ResistanceEventMgrAI.PostName)
# remove the object
self.resistanceEmoteMgr.requestDelete()

View file

@ -0,0 +1,32 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.toonbase import ToontownGlobals, TTLocalizer
from toontown.ai import HolidayBaseAI
class RoamingTrialerWeekendMgrAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'RoamingTrialerWeekendMgrAI')
PostName = 'RoamingTrialerWeekend'
StartStopMsg = 'RoamingTrialerWeekendStartStop'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
# let the holiday system know we started
bboard.post(RoamingTrialerWeekendMgrAI.PostName, True)
# tell everyone race night is starting
simbase.air.newsManager.roamingTrialerWeekendStart()
messenger.send(RoamingTrialerWeekendMgrAI.StartStopMsg)
def stop(self):
# let the holiday system know we stopped
bboard.remove(RoamingTrialerWeekendMgrAI.PostName)
# tell everyone race night is stopping
simbase.air.newsManager.roamingTrialerWeekendEnd()
messenger.send(RoamingTrialerWeekendMgrAI.StartStopMsg)

View file

@ -0,0 +1,216 @@
from direct.directnotify import DirectNotifyGlobal
from direct.interval.IntervalGlobal import *
from toontown.ai import HolidayBaseAI
from otp.otpbase import OTPGlobals
from toontown.toonbase import ToontownGlobals
from toontown.hood import ZoneUtil
from toontown.ai import DistributedScavengerHuntTargetAI
from toontown.scavengerhunt.ScavengerHuntBase import ScavengerHuntBase
from toontown.uberdog.DataStoreAIClient import DataStoreAIClient
from toontown.uberdog import DataStoreGlobals
import time
import pickle
# This dictionary defines the relationship between the scavenger hunt goal id and the zone where the goal is located.
# goalId: zoneId
GOALS = {
# 0 : 2649, # TTC
# 1 : 1834, # DD
# 2 : 4835, # MM
# 3 : 5620, # DG
# 4 : 3707, # BR
# 5 : 9619, # DL
0 : 1000,
1 : 2000,
}
# This dictionary defines the milestones for this scavenger hunt
MILESTONES = {
#0: (GOALS.keys(), 'All Trick-or-Treat goals found'),
0: ((0, 1), 'All Scavenger hunt goals found'),
}
class ScavengerHuntMgrAI(HolidayBaseAI.HolidayBaseAI):
"""
This is the main Scavenger hunt holiday class. It will create
several distributed listeners in selected zones. These listeners
will wait for a toon to trigger something; for example an SC event.
"""
notify = DirectNotifyGlobal.directNotify.newCategory(
'ScavengerHuntMgrAI')
PostName = 'ScavangerHunt'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
self.hunt = None
self.targets = {}
self.storeClient = DataStoreAIClient(air,
DataStoreGlobals.SH,
self.receiveScavengerHuntResults)
@property
def goals(self):
return GOALS
@property
def milestones(self):
return MILESTONES
def start(self):
# Create a unique id for this hunt based on it's start date and time
localTime = time.localtime()
date = (localTime[0],
localTime[1],
localTime[2],
localTime[6],
)
from toontown.ai import HolidayManagerAI
startTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getStartTime(date)
scavengerHuntId = abs(hash(time.ctime(startTime)))
# Create the hunt
self.hunt = ScavengerHuntBase(scavengerHuntId, self.holidayId)
self.hunt.defineGoals(list(self.goals.keys()))
# Send a list with one item in it: [0, (0, 1, 2, 3, 4, 5)]
# This milestone defines the end of the hunt.
self.hunt.defineMilestones((x[0],x[1][0]) for x in list(self.milestones.items()))
self.createListeners()
# let the holiday system know we started
bboard.post(ScavengerHuntMgrAI.PostName)
# make sure the uberdog data store is up for this hunt
self.storeClient.openStore()
def createListeners(self):
"""
Create the listeners that will look for an event in the relavent zone
"""
for id in list(self.goals.keys()):
mgrAI = DistributedScavengerHuntTargetAI.DistributedScavengerHuntTargetAI(self.air,
self.hunt,
id,
self,
)
self.targets[id] = mgrAI
self.targets[id].generateWithRequired(self.goals[id])
def stop(self):
# let the holiday system know we stopped
bboard.remove(ScavengerHuntMgrAI.PostName)
# remove the targetAI's and their distributed counterparts
for zone in list(self.goals.keys()):
self.targets[zone].requestDelete()
self.storeClient.closeStore()
def avatarAttemptingGoal(self, avId, goal):
# We need to know what goals have already been completed
# in order to proceed. Ask the Uberdog for the data.
# We send the goal since when the Uberdog responds we
# still need to match the new goal with the avId.
queryData = (avId, goal)
self.sendScavengerHuntQuery('GetGoals', queryData)
def avatarCompletedGoal(self, avId, goal):
# This will ask the Uberdog to add this new goal
# to the avId's completed list.
queryData = (avId, goal)
self.sendScavengerHuntQuery('AddGoal', queryData)
def sendScavengerHuntQuery(self, qTypeString, qData):
# send a finalized query to the Uberdog.
self.storeClient.sendQuery(qTypeString, qData)
def receiveScavengerHuntResults(self, results):
# This function handles the result message from
# the Uberdog. See the ScavengerHuntDataStore
# class to see data format.
# Indicates that the qId was invalid for this store.
# Should never really happen
if results == None:
return
else:
# extract the queryId, and translate it to it's string
qId, data = results
qType = self.storeClient.getQueryTypeString(qId)
# We're receiving an avatar's new goal and list of completed goals
if qType == 'GetGoals':
avId, goal, done_goals = data
# See what needs to be done
self.__handleResult(avId, done_goals, goal)
# The goal was successfully added to this avId
elif qType == 'AddGoal':
avId, = data
def __handleResult(self, avId, done_goals, goal):
# This is where we check to see if the goal has already been completed.
# If it's already done, let the client know. Otherwise, check to see
# if the scavenger hunt is complete and respond accordingly.
if goal in done_goals:
ScavengerHuntMgrAI.notify.debug(
repr(avId)+' already found Scavenger hunt target '+repr(goal)+': '+repr(self.goals.get(goal, 'INVALID GOAL ID IN '+self.PostName)))
av = self.air.doId2do.get(avId)
milestoneIds = self.hunt.getRecentMilestonesHit(done_goals+[goal], goal)
if milestoneIds:
for id in milestoneIds:
ScavengerHuntMgrAI.notify.debug(
repr(avId)+' hit milestone ' + repr(id) + ': ' + self.milestones.get(milestoneIds[id], [None, 'Undefined milestone id in '+self.PostName])[1])
if (id == 0): # handle found all targets
self.huntCompletedReward(avId, goal)
else:
self.huntGoalFound(avId, goal)
else:
self.huntGoalAlreadyFound(avId)
elif 0 <= goal <= len(list(self.goals.keys())):
ScavengerHuntMgrAI.notify.debug(
repr(avId)+' found Scavenger hunt target '+repr(goal))
av = self.air.doId2do.get(avId)
if not av:
ScavengerHuntMgrAI.notify.warning(
'Tried to send goal feedback to av %s, but they left' % avId)
else:
milestoneIds = self.hunt.getRecentMilestonesHit(done_goals+[goal], goal)
if milestoneIds:
for id in milestoneIds:
ScavengerHuntMgrAI.notify.debug(
repr(avId)+' hit milestone ' + repr(id) + ': ' + self.milestones.get(milestoneIds[id], [None, 'Undefined milestone id in '+self.PostName])[1])
if (id == 0): # handle found all targets
# Wait for the goal found reward to complete
taskMgr.doMethodLater(10, self.huntCompletedReward, repr(avId)+'-huntCompletedReward', extraArgs = [avId, goal, True])
self.huntGoalFound(avId, goal)
else:
self.huntGoalFound(avId, goal)
def huntCompletedReward(self, avId, goal, firstTime = False):
"""
Reward the Toon
"""
pass
def huntGoalAlreadyFound(self, avId):
"""
This goal has already been found
"""
pass
def huntGoalFound(self, avId, goal):
"""
One of the goals in the milestone were found,
so we reward the toon.
"""
pass

View file

@ -0,0 +1,48 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PhasedHolidayAI
from toontown.ai import DistributedSillyMeterMgrAI
from toontown.toonbase import ToontownGlobals
class SillyMeterHolidayAI(PhasedHolidayAI.PhasedHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'SillyMeterHolidayAI')
PostName = 'SillyMeterHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
self.runningState = 1
def start(self):
# instantiate the object
PhasedHolidayAI.PhasedHolidayAI.start(self)
self.SillyMeterMgr = DistributedSillyMeterMgrAI.DistributedSillyMeterMgrAI(
self.air, self.startAndEndTimes, self.phaseDates)
self.SillyMeterMgr.generateWithRequired(ToontownGlobals.UberZone)
# let the holiday system know we started
bboard.post(self.PostName)
def stop(self):
# let the holiday system know we stopped
self.runningState = 0
bboard.remove(self.PostName)
self.SillyMeterMgr.end()
self.SillyMeterMgr.requestDelete()
def forcePhase(self, newPhase):
"""Force our holiday to a certain phase."""
try:
newPhase = int(newPhase)
except:
newPhase = 0
if newPhase >= self.SillyMeterMgr.getNumPhases():
self.notify.warning("newPhase %d invalid in forcePhase" % newPhase)
return
self.curPhase = newPhase
self.SillyMeterMgr.forcePhase(newPhase)
def getRunningState(self):
return self.runningState

View file

@ -5,7 +5,6 @@ from panda3d.toontown import *
from otp.ai.AIZoneData import AIZoneDataStore from otp.ai.AIZoneData import AIZoneDataStore
from otp.ai.TimeManagerAI import TimeManagerAI from otp.ai.TimeManagerAI import TimeManagerAI
from otp.distributed.OtpDoGlobals import * from otp.distributed.OtpDoGlobals import *
from otp.friends.FriendManagerAI import FriendManagerAI
from toontown.ai.HolidayManagerAI import HolidayManagerAI from toontown.ai.HolidayManagerAI import HolidayManagerAI
from toontown.ai.NewsManagerAI import NewsManagerAI from toontown.ai.NewsManagerAI import NewsManagerAI
from toontown.ai.WelcomeValleyManagerAI import WelcomeValleyManagerAI from toontown.ai.WelcomeValleyManagerAI import WelcomeValleyManagerAI
@ -20,6 +19,7 @@ from toontown.coghq.PromotionManagerAI import PromotionManagerAI
from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI
from toontown.distributed.ToontownDistrictStatsAI import ToontownDistrictStatsAI from toontown.distributed.ToontownDistrictStatsAI import ToontownDistrictStatsAI
from toontown.distributed.ToontownInternalRepository import ToontownInternalRepository from toontown.distributed.ToontownInternalRepository import ToontownInternalRepository
from toontown.estate.EstateManagerAI import EstateManagerAI
from toontown.hood import ZoneUtil from toontown.hood import ZoneUtil
from toontown.hood.BRHoodDataAI import BRHoodDataAI from toontown.hood.BRHoodDataAI import BRHoodDataAI
from toontown.hood.BossbotHQDataAI import BossbotHQDataAI from toontown.hood.BossbotHQDataAI import BossbotHQDataAI
@ -94,6 +94,9 @@ class ToontownAIRepository(ToontownInternalRepository):
self.trophyMgr = None self.trophyMgr = None
self.safeZoneManager = None self.safeZoneManager = None
self.magicWordManager = None self.magicWordManager = None
self.friendManager = None
self.toontownFriendsManager = None
self.estateMgr = None
self.partyManager = None self.partyManager = None
self.zoneTable = {} self.zoneTable = {}
self.dnaStoreMap = {} self.dnaStoreMap = {}
@ -119,13 +122,14 @@ class ToontownAIRepository(ToontownInternalRepository):
# Setup necessary files and things. # Setup necessary files and things.
self.setupFiles() self.setupFiles()
# Create our global objects.
self.notify.info('Creating global objects...')
self.createGlobals()
# Create our local objects. # Create our local objects.
self.notify.info('Creating local objects...') self.notify.info('Creating local objects...')
self.createLocals() self.createLocals()
# Create our global objects.
self.notify.info('Creating global objects...')
self.createGlobals()
# Create our zones. # Create our zones.
self.notify.info('Creating zones...') self.notify.info('Creating zones...')
@ -225,6 +229,19 @@ class ToontownAIRepository(ToontownInternalRepository):
self.magicWordManager = ToontownMagicWordManagerAI(self) self.magicWordManager = ToontownMagicWordManagerAI(self)
self.magicWordManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.magicWordManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
# Generate our friend manager...
self.friendManager = self.generateGlobalObject(OTP_DO_ID_FRIEND_MANAGER, 'FriendManager')
if __astron__:
# Generate our Toontown friends manager...
# TODO: Is this Astron specific?
self.toontownFriendsManager = self.generateGlobalObject(OTP_DO_ID_TOONTOWN_FRIENDS_MANAGER,
'ToontownFriendsManager')
# Generate our estate manager...
self.estateMgr = EstateManagerAI(self)
self.estateMgr.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
# Generate our Tutorial manager... # Generate our Tutorial manager...
self.tutorialManager = TutorialManagerAI(self) self.tutorialManager = TutorialManagerAI(self)
self.tutorialManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.tutorialManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
@ -233,9 +250,6 @@ class ToontownAIRepository(ToontownInternalRepository):
self.partyManager = DistributedPartyManagerAI(self) self.partyManager = DistributedPartyManagerAI(self)
self.partyManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.partyManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
self.friendManager = FriendManagerAI(self)
self.friendManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
def generateHood(self, hoodConstructor, zoneId): def generateHood(self, hoodConstructor, zoneId):
# Bossbot HQ doesn't use DNA, so we skip over that. # Bossbot HQ doesn't use DNA, so we skip over that.
if zoneId != ToontownGlobals.BossbotHQ: if zoneId != ToontownGlobals.BossbotHQ:
@ -507,6 +521,33 @@ class ToontownAIRepository(ToontownInternalRepository):
def trueUniqueName(self, idString): def trueUniqueName(self, idString):
return self.uniqueName(idString) return self.uniqueName(idString)
def makeFriends(self, avatarAId, avatarBId, flags, context):
"""
Requests to make a friendship between avatarA and avatarB with
the indicated flags (or upgrade an existing friendship with
the indicated flags). The context is any arbitrary 32-bit
integer. When the friendship is made, or the operation fails,
the "makeFriendsReply" event is generated, with two
parameters: an integer result code, and the supplied context.
"""
if __astron__:
self.toontownFriendsManager.sendMakeFriends(avatarAId, avatarBId, flags, context)
def requestSecret(self, requesterId):
"""
Requests a "secret" from the friends manager. This is a
unique string that will be associated with the indicated
requesterId, for the purposes of authenticating true-life
friends.
When the secret is ready, a "requestSecretReply" message will
be thrown with three parameters: the result code (0 or 1,
indicating failure or success), the generated secret, and the
requesterId again.
"""
if __astron__:
self.toontownFriendsManager.sendRequestSecret(requesterId)
def setupFiles(self): def setupFiles(self):
if not os.path.exists(self.dataFolder): if not os.path.exists(self.dataFolder):
os.mkdir(self.dataFolder) os.mkdir(self.dataFolder)

View file

@ -0,0 +1,16 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PropBuffHolidayAI
from toontown.ai import DistributedPhaseEventMgrAI
from toontown.toonbase import ToontownGlobals
class TrashcanBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'TrashcanBuffHolidayAI')
PostName = 'TrashcanBuffHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)

View file

@ -0,0 +1,47 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.ai import PhasedHolidayAI
from toontown.ai import DistributedTrashcanZeroMgrAI
from toontown.toonbase import ToontownGlobals
class TrashcanZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'TrashcanZeroHolidayAI')
PostName = 'trashcanZeroHoliday'
def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
def start(self):
# instantiate the object
PhasedHolidayAI.PhasedHolidayAI.start(self)
self.trashcanZeroMgr = DistributedTrashcanZeroMgrAI.DistributedTrashcanZeroMgrAI (
self.air, self.startAndEndTimes, self.phaseDates)
self.trashcanZeroMgr.generateWithRequired(ToontownGlobals.UberZone)
# let the holiday system know we started
bboard.post(self.PostName)
def stop(self):
# let the holiday system know we stopped
bboard.remove(self.PostName)
# remove the object
#self.resistanceEmoteMgr.requestDelete()
self.trashcanZeroMgr.requestDelete()
def forcePhase(self, newPhase):
"""Force our holiday to a certain phase. Returns true if succesful"""
result = False
try:
newPhase = int(newPhase)
except:
newPhase = 0
if newPhase >= self.trashcanZeroMgr.getNumPhases():
self.notify.warning("newPhase %d invalid in forcePhase" % newPhase)
return
self.curPhase = newPhase
self.trashcanZeroMgr.forcePhase(newPhase)
result = True
return result

View file

@ -0,0 +1,103 @@
from . import ScavengerHuntMgrAI
from direct.directnotify import DirectNotifyGlobal
from toontown.toonbase import ToontownGlobals
from toontown.ai import DistributedTrickOrTreatTargetAI
from otp.otpbase import OTPGlobals
import time
GOALS = {
0 : 2649, # TTC
1 : 1834, # DD
2 : 4835, # MM
3 : 5620, # DG
4 : 3707, # BR
5 : 9619, # DL
}
# This dictionary defines the milestones for this scavenger hunt
MILESTONES = {
0: ((0, 1, 2, 3, 4, 5), 'All Trick-or-Treat goals found'),
}
class TrickOrTreatMgrAI(ScavengerHuntMgrAI.ScavengerHuntMgrAI):
"""
This is the TrickOrTreat manager that extends the scanvenger hunt
by providing unique rewards and milestones.
"""
notify = DirectNotifyGlobal.directNotify.newCategory('TrickOrTreatMgrAI')
def __init__(self, air, holidayId):
ScavengerHuntMgrAI.ScavengerHuntMgrAI.__init__(self, air, holidayId)
def createListeners(self):
"""
Create the listeners that will look for an event in the relavent zone
"""
for id in list(self.goals.keys()):
mgrAI = DistributedTrickOrTreatTargetAI.DistributedTrickOrTreatTargetAI(self.air,
self.hunt,
id,
self,
)
self.targets[id] = mgrAI
self.targets[id].generateWithRequired(self.goals[id])
@property
def goals(self):
return GOALS
@property
def milestones(self):
return MILESTONES
def huntCompletedReward(self, avId, goal, firstTime = False):
"""
Reward the Toon for completing the TrickOrTreat with
a pumpkin head
"""
if firstTime:
self.air.writeServerEvent('pumpkinHeadEarned', avId, 'Trick-or-Treat scavenger hunt complete.')
av = self.air.doId2do.get(avId)
localTime = time.localtime()
date = (localTime[0],
localTime[1],
localTime[2],
localTime[6],
)
from toontown.ai import HolidayManagerAI
endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(date)
endTime += ToontownGlobals.TOT_REWARD_END_OFFSET_AMOUNT
if not av:
self.notify.warning(
'Tried to send milestone feedback to av %s, but they left' % avId)
else:
av.b_setCheesyEffect(OTPGlobals.CEPumpkin, 0, (time.time()/60)+1)
#av.b_setCheesyEffect(OTPGlobals.CEPumpkin, 0, endTime/60)
def huntGoalAlreadyFound(self, avId):
"""
This goal has already been found
"""
av = self.air.doId2do.get(avId)
if not av:
self.notify.warning(
'Tried to send goal feedback to av %s, but they left' % avId)
else:
av.sendUpdate('trickOrTreatTargetMet', [0])
def huntGoalFound(self, avId, goal):
"""
One of the goals in the milestone were found,
so we reward the toon.
"""
av = self.air.doId2do.get(avId)
# Do all the updates at once
av.addMoney(ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT)
self.avatarCompletedGoal(avId, goal)
# Start jellybean reward effect
av.sendUpdate('trickOrTreatTargetMet', [ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT])

View file

@ -0,0 +1,23 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.toonbase import ToontownGlobals, TTLocalizer
from toontown.ai import HolidayBaseAI
class ValentinesDayMgrAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'ValentinesDayMgrAI')
PostName = 'ValentinesDay'
StartStopMsg = 'ValentinesDayStartStop'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
# Let the holiday system know we started
bboard.post(ValentinesDayMgrAI.PostName, True)
def stop(self):
# Let the holiday system know we stopped
bboard.remove(ValentinesDayMgrAI.PostName)

View file

@ -0,0 +1,112 @@
from . import ScavengerHuntMgrAI
from direct.directnotify import DirectNotifyGlobal
from toontown.toonbase import ToontownGlobals
from toontown.ai import DistributedWinterCarolingTargetAI
from otp.otpbase import OTPGlobals
import time
GOALS = {
0 : 2659, # Joy Buzzer to the world, Silly Street, Toontown Central
1 : 1707, # Gifts With A Porpoise, Seaweed Street, Donalds Dock
2 : 5626, # Pine Needle Crafts, Elm Street, Daisy's Garden
3 : 4614, # Shave and Haircut for a song, Alto Avenue, Minnie's Melodyland
4 : 3828, # Snowman's Land, Polar Place, The Brrrgh
5 : 9720, # Talking in Your Sleep Voice Training, Pajama Place, Donald's Dreamland
}
# This dictionary defines the milestones for this scavenger hunt
MILESTONES = {
0: ((0, 1, 2, 3, 4, 5), 'All Winter Caroling goals found'),
}
class WinterCarolingMgrAI(ScavengerHuntMgrAI.ScavengerHuntMgrAI):
"""
This is the WinterCaroling manager that extends the scanvenger hunt
by providing unique rewards and milestones.
"""
notify = DirectNotifyGlobal.directNotify.newCategory('WinterCarolingMgrAI')
def __init__(self, air, holidayId):
ScavengerHuntMgrAI.ScavengerHuntMgrAI.__init__(self, air, holidayId)
def createListeners(self):
"""
Create the listeners that will look for an event in the relavent zone
"""
for id in list(self.goals.keys()):
mgrAI = DistributedWinterCarolingTargetAI.DistributedWinterCarolingTargetAI(self.air,
self.hunt,
id,
self,
)
self.targets[id] = mgrAI
self.targets[id].generateWithRequired(self.goals[id])
@property
def goals(self):
return GOALS
@property
def milestones(self):
return MILESTONES
def huntCompletedReward(self, avId, goal, firstTime = False):
"""
Reward the Toon for completing the WinterCaroling with
a pumpkin head
"""
if firstTime:
self.air.writeServerEvent('pumpkinHeadEarned', avId, 'WinterCaroling scavenger hunt complete.')
av = self.air.doId2do.get(avId)
localTime = time.localtime()
date = (localTime[0],
localTime[1],
localTime[2],
localTime[6],
)
from toontown.ai import HolidayManagerAI
endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(date)
startTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getStartTime(date)
if endTime < startTime:
end = time.localtime(endTime)
start = time.localtime(startTime)
newDate = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].adjustDate(date)
endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(newDate)
endTime += ToontownGlobals.TOT_REWARD_END_OFFSET_AMOUNT
if not av:
self.notify.warning(
'Tried to send milestone feedback to av %s, but they left' % avId)
else:
#av.b_setCheesyEffect(OTPGlobals.CESnowMan, 0, (time.time()/60)+1)
av.b_setCheesyEffect(OTPGlobals.CESnowMan, 0, endTime/60)
def huntGoalAlreadyFound(self, avId):
"""
This goal has already been found
"""
av = self.air.doId2do.get(avId)
if not av:
self.notify.warning(
'Tried to send goal feedback to av %s, but they left' % avId)
else:
av.sendUpdate('winterCarolingTargetMet', [0])
def huntGoalFound(self, avId, goal):
"""
One of the goals in the milestone were found,
so we reward the toon.
"""
av = self.air.doId2do.get(avId)
# Do all the updates at once
av.addMoney(ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT)
self.avatarCompletedGoal(avId, goal)
# Start jellybean reward effect
av.sendUpdate('winterCarolingTargetMet', [ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT])

View file

@ -101,10 +101,12 @@ def teleportIn(attack, npc, pos = Point3(0, 0, 0), hpr = Vec3(180.0, 0.0, 0.0)):
def teleportOut(attack, npc): def teleportOut(attack, npc):
if npc.style.getGender() == 'm': # instead of gender check the torso type
a = ActorInterval(npc, 'bow')
else: if npc.style.torso[1] == 'd':
a = ActorInterval(npc, 'curtsy') a = ActorInterval(npc, 'curtsy')
else:
a = ActorInterval(npc, 'bow')
b = Func(npc.setChatAbsolute, TTLocalizer.MovieNPCSOSGoodbye, CFSpeech | CFTimeout) b = Func(npc.setChatAbsolute, TTLocalizer.MovieNPCSOSGoodbye, CFSpeech | CFTimeout)
c = npc.getTeleportOutTrack() c = npc.getTeleportOutTrack()
d = Func(npc.removeActive) d = Func(npc.removeActive)

View file

@ -18,41 +18,9 @@ class CatalogAccessoryItem(CatalogItem.CatalogItem):
def storedInTrunk(self): def storedInTrunk(self):
return 1 return 1
def notOfferedTo(self, avatar):
article = AccessoryTypes[self.accessoryType][ATArticle]
if article in [AHat,
AGlasses,
ABackpack,
AShoes]:
return 0
forBoys = article in [ABoysHat,
ABoysGlasses,
ABoysBackpack,
ABoysShoes]
if avatar.getStyle().getGender() == 'm':
return not forBoys
else:
return forBoys
def forBoysOnly(self):
article = AccessoryTypes[self.accessoryType][ATArticle]
if article in [ABoysHat,
ABoysGlasses,
ABoysBackpack,
ABoysShoes]:
return 1
else:
return 0
def forGirlsOnly(self):
article = AccessoryTypes[self.accessoryType][ATArticle]
if article in [AGirlsHat,
AGirlsGlasses,
AGirlsBackpack,
AGirlsShoes]:
return 1
else:
return 0
def getPurchaseLimit(self): def getPurchaseLimit(self):
return 1 return 1

View file

@ -312,29 +312,9 @@ class CatalogClothingItem(CatalogItem.CatalogItem):
def storedInCloset(self): def storedInCloset(self):
return 1 return 1
def notOfferedTo(self, avatar):
article = ClothingTypes[self.clothingType][CTArticle]
if article == AShirt or article == AShorts:
return 0
forBoys = (article == ABoysShirt or article == ABoysShorts)
if avatar.getStyle().getGender() == 'm':
return not forBoys
else:
return forBoys
def forBoysOnly(self):
article = ClothingTypes[self.clothingType][CTArticle]
if article == ABoysShirt or article == ABoysShorts:
return 1
else:
return 0
def forGirlsOnly(self):
article = ClothingTypes[self.clothingType][CTArticle]
if article == AGirlsShirt or article == AGirlsSkirt or article == AGirlsShorts:
return 1
else:
return 0
def getPurchaseLimit(self): def getPurchaseLimit(self):
return 1 return 1
@ -412,11 +392,10 @@ class CatalogClothingItem(CatalogItem.CatalogItem):
defn = ToonDNA.BottomStyles[str] defn = ToonDNA.BottomStyles[str]
dna.botTex = defn[0] dna.botTex = defn[0]
dna.botTexColor = defn[1][self.colorIndex] dna.botTexColor = defn[1][self.colorIndex]
if dna.getGender() == 'f':
try: try:
bottomPair = ToonDNA.GirlBottoms[dna.botTex] bottomPair = ToonDNA.Bottoms[dna.botTex]
except: except:
bottomPair = ToonDNA.GirlBottoms[0] bottomPair = ToonDNA.Bottoms[0]
if dna.torso[1] == 's' and bottomPair[1] == ToonDNA.SKIRT: if dna.torso[1] == 's' and bottomPair[1] == ToonDNA.SKIRT:
dna.torso = dna.torso[0] + 'd' dna.torso = dna.torso[0] + 'd'

View file

@ -936,15 +936,7 @@ class CatalogFurnitureItem(CatalogAtticItem.CatalogAtticItem):
return None return None
return None return None
def notOfferedTo(self, avatar):
if self.getFlags() & FLCloset or self.getFlags() & FLTrunk:
decade = self.furnitureType - self.furnitureType % 10
forBoys = (decade == 500 or decade == 4000)
if avatar.getStyle().getGender() == 'm':
return not forBoys
else:
return forBoys
return 0
def isDeletable(self): def isDeletable(self):
return self.getFlags() & (FLBank | FLCloset | FLPhone | FLTrunk) == 0 return self.getFlags() & (FLBank | FLCloset | FLPhone | FLTrunk) == 0
@ -1116,10 +1108,7 @@ class CatalogFurnitureItem(CatalogAtticItem.CatalogAtticItem):
def nextAvailableCloset(avatar, duplicateItems): def nextAvailableCloset(avatar, duplicateItems):
if avatar.getStyle().getGender() == 'm':
index = 0 index = 0
else:
index = 1
if not hasattr(avatar, 'maxClothes'): if not hasattr(avatar, 'maxClothes'):
return None return None
closetIds = ClothesToCloset.get(avatar.getMaxClothes()) closetIds = ClothesToCloset.get(avatar.getMaxClothes())
@ -1140,10 +1129,7 @@ def nextAvailableCloset(avatar, duplicateItems):
def get50ItemCloset(avatar, duplicateItems): def get50ItemCloset(avatar, duplicateItems):
if avatar.getStyle().getGender() == 'm':
index = 0 index = 0
else:
index = 1
closetId = MaxClosetIds[index] closetId = MaxClosetIds[index]
item = CatalogFurnitureItem(closetId) item = CatalogFurnitureItem(closetId)
if item in avatar.onOrder or item in avatar.mailboxContents: if item in avatar.onOrder or item in avatar.mailboxContents:
@ -1168,10 +1154,8 @@ def getAllClosets():
def get50ItemTrunk(avatar, duplicateItems): def get50ItemTrunk(avatar, duplicateItems):
if avatar.getStyle().getGender() == 'm':
index = 0 index = 0
else:
index = 1
trunkId = MaxTrunkIds[index] trunkId = MaxTrunkIds[index]
item = CatalogFurnitureItem(trunkId) item = CatalogFurnitureItem(trunkId)
if item in avatar.onOrder or item in avatar.mailboxContents: if item in avatar.onOrder or item in avatar.mailboxContents:

View file

@ -1642,7 +1642,7 @@ class CatalogGenerator:
item = MetaItems[item] item = MetaItems[item]
selection = [] selection = []
if isinstance(item, CatalogItem.CatalogItem): if isinstance(item, CatalogItem.CatalogItem):
if not item.notOfferedTo(avatar):
item.saleItem = saleItem item.saleItem = saleItem
selection.append(item) selection.append(item)
elif item != None: elif item != None:
@ -1661,7 +1661,7 @@ class CatalogGenerator:
index = random.randrange(len(list)) index = random.randrange(len(list))
item = list[index] item = list[index]
del list[index] del list[index]
while item.notOfferedTo(avatar) or item.reachedPurchaseLimit(avatar) or item in duplicateItems or item in avatar.backCatalog or item in avatar.weeklyCatalog: while item.reachedPurchaseLimit(avatar) or item in duplicateItems or item in avatar.backCatalog or item in avatar.weeklyCatalog:
if len(list) == 0: if len(list) == 0:
return None return None
index = random.randrange(len(list)) index = random.randrange(len(list))

View file

@ -82,8 +82,7 @@ class CatalogItem:
def storedInAttic(self): def storedInAttic(self):
return 0 return 0
def notOfferedTo(self, avatar):
return 0
def getPurchaseLimit(self): def getPurchaseLimit(self):
return 0 return 0
@ -120,11 +119,7 @@ class CatalogItem:
def isRental(self): def isRental(self):
return 0 return 0
def forBoysOnly(self):
return 0
def forGirlsOnly(self):
return 0
def setLoyaltyRequirement(self, days): def setLoyaltyRequirement(self, days):
self.loyaltyDays = days self.loyaltyDays = days

View file

@ -484,13 +484,8 @@ class CatalogItemPanel(DirectFrame):
return return
elif self.parentCatalogScreen.gotAvatar == 1: elif self.parentCatalogScreen.gotAvatar == 1:
avatar = self.parentCatalogScreen.giftAvatar avatar = self.parentCatalogScreen.giftAvatar
if self['item'].forBoysOnly() and avatar.getStyle().getGender() == 'f' or self['item'].forGirlsOnly() and avatar.getStyle().getGender() == 'm':
self.giftButton.show() if self['item'].reachedPurchaseLimit(avatar):
self.giftButton['state'] = DGG.DISABLED
auxText = TTLocalizer.CatalogNoFit
self.auxText['text'] = auxText
return
elif self['item'].reachedPurchaseLimit(avatar):
self.giftButton.show() self.giftButton.show()
self.giftButton['state'] = DGG.DISABLED self.giftButton['state'] = DGG.DISABLED
auxText = TTLocalizer.CatalogPurchasedGiftText auxText = TTLocalizer.CatalogPurchasedGiftText

View file

@ -63,7 +63,6 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
self.toons = {} self.toons = {}
if self.http.getVerifySsl() != HTTPClient.VSNoVerify: if self.http.getVerifySsl() != HTTPClient.VSNoVerify:
self.http.setVerifySsl(HTTPClient.VSNoDateCheck) self.http.setVerifySsl(HTTPClient.VSNoDateCheck)
#prepareAvatar(self.http)
self.__forbidCheesyEffects = 0 self.__forbidCheesyEffects = 0
self.friendManager = None self.friendManager = None
self.speedchatRelay = None self.speedchatRelay = None
@ -82,6 +81,7 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
self.toontownTimeManager = ToontownTimeManager.ToontownTimeManager() self.toontownTimeManager = ToontownTimeManager.ToontownTimeManager()
self.avatarFriendsManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_AVATAR_FRIENDS_MANAGER, 'AvatarFriendsManager') self.avatarFriendsManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_AVATAR_FRIENDS_MANAGER, 'AvatarFriendsManager')
self.playerFriendsManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_PLAYER_FRIENDS_MANAGER, 'TTPlayerFriendsManager') self.playerFriendsManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_PLAYER_FRIENDS_MANAGER, 'TTPlayerFriendsManager')
self.toontownFriendsManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_TOONTOWN_FRIENDS_MANAGER, 'ToontownFriendsManager')
self.speedchatRelay = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_TOONTOWN_SPEEDCHAT_RELAY, 'TTSpeedchatRelay') self.speedchatRelay = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_TOONTOWN_SPEEDCHAT_RELAY, 'TTSpeedchatRelay')
self.deliveryManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER, 'DistributedDeliveryManager') self.deliveryManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER, 'DistributedDeliveryManager')
if ConfigVariableBool('want-code-redemption', 1).value: if ConfigVariableBool('want-code-redemption', 1).value:
@ -119,17 +119,16 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
for head in ToonDNA.getHeadList(species): for head in ToonDNA.getHeadList(species):
for torso in ToonDNA.toonTorsoTypes: for torso in ToonDNA.toonTorsoTypes:
for legs in ToonDNA.toonLegTypes: for legs in ToonDNA.toonLegTypes:
for gender in ('m', 'f'): print('species: %s, head: %s, torso: %s, legs: %s' % (species,
print('species: %s, head: %s, torso: %s, legs: %s, gender: %s' % (species,
head, head,
torso, torso,
legs, legs,
gender)) ))
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToon((head, dna.newToon((head,
torso, torso,
legs, legs
gender)) ))
toon = Toon.Toon() toon = Toon.Toon()
try: try:
toon.setDNA(dna) toon.setDNA(dna)
@ -423,6 +422,9 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
pad.delayDelete.destroy() pad.delayDelete.destroy()
def __sendGetAvatarDetails(self, avId): def __sendGetAvatarDetails(self, avId):
if __astron__:
self.toontownFriendsManager.sendGetAvatarDetailsRequest(avId)
else:
datagram = PyDatagram() datagram = PyDatagram()
avatar = self.__queryAvatarMap[avId].avatar avatar = self.__queryAvatarMap[avId].avatar
datagram.addUint16(avatar.getRequestID()) datagram.addUint16(avatar.getRequestID())
@ -834,6 +836,9 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
return 1 return 1
def removeFriend(self, avatarId): def removeFriend(self, avatarId):
if __astron__:
self.toontownFriendsManager.sendRemoveFriend(avatarId)
else:
base.localAvatar.sendUpdate('friendsNotify', [base.localAvatar.doId, 1], sendToId=avatarId) base.localAvatar.sendUpdate('friendsNotify', [base.localAvatar.doId, 1], sendToId=avatarId)
datagram = PyDatagram() datagram = PyDatagram()
datagram.addUint16(CLIENT_REMOVE_FRIEND) datagram.addUint16(CLIENT_REMOVE_FRIEND)
@ -853,11 +858,11 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
self.friendsListError = 0 self.friendsListError = 0
def sendGetFriendsListRequest(self): def sendGetFriendsListRequest(self):
if __astron__:
print('sendGetFriendsListRequest TODO')
else:
self.friendsMapPending = 1 self.friendsMapPending = 1
self.friendsListError = 0 self.friendsListError = 0
if __astron__:
self.toontownFriendsManager.sendGetFriendsListRequest()
else:
datagram = PyDatagram() datagram = PyDatagram()
datagram.addUint16(CLIENT_GET_FRIEND_LIST) datagram.addUint16(CLIENT_GET_FRIEND_LIST)
self.send(datagram) self.send(datagram)
@ -895,6 +900,7 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
def handleGetFriendsList(self, di): def handleGetFriendsList(self, di):
error = di.getUint8() error = di.getUint8()
friends = []
if error: if error:
self.notify.warning('Got error return from friends list.') self.notify.warning('Got error return from friends list.')
self.friendsListError = 1 self.friendsListError = 1
@ -904,9 +910,15 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
doId = di.getUint32() doId = di.getUint32()
name = di.getString() name = di.getString()
dnaString = di.getBlob() dnaString = di.getBlob()
petId = di.getUint32()
friends.append((doId, name, dnaString, petId))
self.setFriendsMap(friends)
def setFriendsMap(self, friends):
for doId, name, dnaString, petId in friends:
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.makeFromNetString(dnaString) dna.makeFromNetString(dnaString)
petId = di.getUint32()
handle = FriendHandle.FriendHandle(doId, name, dna, petId) handle = FriendHandle.FriendHandle(doId, name, dna, petId)
self.friendsMap[doId] = handle self.friendsMap[doId] = handle
if doId in self.friendsOnline: if doId in self.friendsOnline:
@ -916,13 +928,12 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
handle.setCommonAndWhitelistChatFlags(*self.friendPendingChatSettings[doId]) handle.setCommonAndWhitelistChatFlags(*self.friendPendingChatSettings[doId])
if base.wantPets and base.localAvatar.hasPet(): if base.wantPets and base.localAvatar.hasPet():
def handleAddedPet(): def handleAddedPet():
self.friendsMapPending = 0 self.friendsMapPending = 0
messenger.send('friendsMapComplete') messenger.send('friendsMapComplete')
self.addPetToFriendsMap(handleAddedPet) self.addPetToFriendsMap(handleAddedPet)
return return
self.friendsMapPending = 0 self.friendsMapPending = 0
messenger.send('friendsMapComplete') messenger.send('friendsMapComplete')
@ -961,6 +972,9 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
commonChatFlags = di.getUint8() commonChatFlags = di.getUint8()
if di.getRemainingSize() > 0: if di.getRemainingSize() > 0:
whitelistChatFlags = di.getUint8() whitelistChatFlags = di.getUint8()
self.setFriendOnline(doId, commonChatFlags, whitelistChatFlags)
def setFriendOnline(self, doId, commonChatFlags, whitelistChatFlags):
self.notify.debug('Friend %d now online. common=%d whitelist=%d' % (doId, commonChatFlags, whitelistChatFlags)) self.notify.debug('Friend %d now online. common=%d whitelist=%d' % (doId, commonChatFlags, whitelistChatFlags))
if doId not in self.friendsOnline: if doId not in self.friendsOnline:
self.friendsOnline[doId] = self.identifyFriend(doId) self.friendsOnline[doId] = self.identifyFriend(doId)
@ -970,6 +984,9 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
def handleFriendOffline(self, di): def handleFriendOffline(self, di):
doId = di.getUint32() doId = di.getUint32()
self.setFriendOffline(doId)
def setFriendOffline(self, doId):
self.notify.debug('Friend %d now offline.' % doId) self.notify.debug('Friend %d now offline.' % doId)
try: try:
del self.friendsOnline[doId] del self.friendsOnline[doId]

View file

@ -0,0 +1,564 @@
#################################################################
# class: BingoManagerAI.py
#
# Purpose: Manages the Bingo Night Holiday for all ponds in all
# hoods. It generates PondBingoManagerAI objects for
# every pond and shuts them down respectively. In
# addition, it should handle all Stat collection such
# as top jackpot of the night, top bingo players, and
# so forth.
#
# Note: Eventually, this will derive from the HolidayBase class
# and run each and ever Bingo Night, whenever that has
# been decided upon.
#################################################################
#################################################################
# Direct Specific Modules
#################################################################
from direct.distributed import DistributedObjectAI
from direct.distributed.ClockDelta import *
from direct.directnotify import DirectNotifyGlobal
from otp.otpbase import PythonUtil
from direct.task import Task
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.estate import DistributedEstateAI
from toontown.fishing import BingoGlobals
from toontown.fishing import DistributedFishingPondAI
from toontown.fishing import DistributedPondBingoManagerAI
from direct.showbase import RandomNumGen
from toontown.toonbase import ToontownGlobals
from toontown.hood import ZoneUtil
#################################################################
# Python Specific Modules
#################################################################
import pickle
import os
import time
#################################################################
# Globals and Constants
#################################################################
TTG = ToontownGlobals
BG = BingoGlobals
class BingoManagerAI(object):
# __metaclass__ = PythonUtil.Singleton
notify = DirectNotifyGlobal.directNotify.newCategory("BingoManagerAI")
#notify.setDebug(True)
#notify.setInfo(True)
serverDataFolder = simbase.config.GetString('server-data-folder', "dependencies/backups/bingo")
DefaultReward = { TTG.DonaldsDock: [BG.MIN_SUPER_JACKPOT, 1],
TTG.ToontownCentral: [BG.MIN_SUPER_JACKPOT, 1],
TTG.TheBrrrgh: [BG.MIN_SUPER_JACKPOT, 1],
TTG.MinniesMelodyland: [BG.MIN_SUPER_JACKPOT, 1],
TTG.DaisyGardens: [BG.MIN_SUPER_JACKPOT, 1],
TTG.DonaldsDreamland: [BG.MIN_SUPER_JACKPOT, 1],
TTG.MyEstate: [BG.MIN_SUPER_JACKPOT, 1] }
############################################################
# Method: __init__
# Purpose: This method initializes the BingoManagerAI object
# and generates the PondBingoManagerAI.
# Input: air - The AI Repository.
# Output: None
############################################################
def __init__(self, air):
self.air = air
# Dictionaries for quick reference to the DPMAI
self.doId2do = {}
self.zoneId2do = {}
self.hood2doIdList = { TTG.DonaldsDock: [],
TTG.ToontownCentral: [],
TTG.TheBrrrgh: [],
TTG.MinniesMelodyland: [],
TTG.DaisyGardens: [],
TTG.DonaldsDreamland: [],
TTG.MyEstate: [] }
self.__hoodJackpots = {}
self.finalGame = BG.NORMAL_GAME
self.shard = str(air.districtId)
self.waitTaskName = 'waitForIntermission'
# Generate the Pond Bingo Managers
self.generateBingoManagers()
############################################################
# Method: start
# Purpose: This method "starts" each PondBingoManager for
# the Bingo Night Holidy.
# Input: None
# Output: None
############################################################
def start(self):
# Iterate through keys and change into "active" state
# for the pondBingoManagerAI
self.notify.info("Starting Bingo Night Event: %s" % (time.ctime()))
self.air.bingoMgr = self
# Determine current time so that we can gracefully handle
# an AI crash or reboot during Bingo Night.
currentMin = time.localtime()[4]
self.timeStamp = globalClockDelta.getRealNetworkTime()
initState = ((currentMin < BG.HOUR_BREAK_MIN) and ['Intro'] or ['Intermission'])[0]
# CHEATS
#initState = 'Intermission'
for do in list(self.doId2do.values()):
do.startup(initState)
self.waitForIntermission()
# tell everyone bingo night is starting
simbase.air.newsManager.bingoStart()
############################################################
# Method: stop
# Purpose: This method begins the process of shutting down
# bingo night. It is called whenever the
# BingoNightHolidayAI is told to close for the
# evening.
# Input: None
# Output: None
############################################################
def stop(self):
self.__startCloseEvent()
############################################################
# Method: __shutdown
# Purpose: This method performs the actual shutdown sequence
# for the pond bingo manager. By this point, all
# of the PondBingoManagerAIs should have shutdown
# so we can safely close.
# Input: None
# Output: None
############################################################
def shutdown(self):
self.notify.info('__shutdown: Shutting down BingoManager')
# tell everyone bingo night is stopping
simbase.air.newsManager.bingoEnd()
if self.doId2do:
#self.notify.warning('__shutdown: Not all PondBingoManagers have shutdown! Manual Shutdown for Memory sake.')
for bingoMgr in list(self.doId2do.values()):
self.notify.info("__shutdown: shutting down PondBinfoManagerAI in zone %s" % bingoMgr.zoneId)
bingoMgr.shutdown()
self.doId2do.clear()
del self.doId2do
self.air.bingoMgr = None
del self.air
del self.__hoodJackpots
############################################################
# Method: __resumeBingoNight
# Purpose: This method resumes Bingo Night after an
# an intermission has taken place. This should
# start on the hour.
# Input: None
# Output: None
############################################################
def __resumeBingoNight(self, task):
self.__hoodJackpots = self.load()
for bingoMgr in list(self.doId2do.values()):
if bingoMgr.isGenerated():
if self.finalGame:
bingoMgr.setFinalGame(self.finalGame)
bingoMgr.resumeBingoNight()
timeToWait = BG.getGameTime(BG.BLOCKOUT_CARD) + BG.TIMEOUT_SESSION + 5.0
taskMgr.doMethodLater(timeToWait, self.__handleSuperBingoClose, 'SuperBingoClose')
# If we have another game after this, then do not want to generate a
# new task to wait for the next intermission.
return Task.done
############################################################
# Method: __handleSuperBingoClose
# Purpose: This method is responsible for logging the
# current hood jackpot amounts to the .jackpot
# "database" file. In addition, it initiates the
# shutdown of the BingoManagerAI if the final
# game of the evening has been played.
# Input: task - a task that is spawned by a doMethodLater
# Output: None
############################################################
def __handleSuperBingoClose(self, task):
# Save Jackpot Data to File
self.notify.info("handleSuperBingoClose: Saving Hood Jackpots to DB")
self.notify.info("handleSuperBingoClose: hoodJackpots %s" %(self.__hoodJackpots))
for hood in list(self.__hoodJackpots.keys()):
if self.__hoodJackpots[hood][1]:
self.__hoodJackpots[hood][0] += BG.ROLLOVER_AMOUNT
# clamp it if it exceeds jackpot total
if self.__hoodJackpots[hood][0] > BG.MAX_SUPER_JACKPOT:
self.__hoodJackpots[hood][0] = BG.MAX_SUPER_JACKPOT
else:
self.__hoodJackpots[hood][1] = BG.MIN_SUPER_JACKPOT
taskMgr.remove(task)
self.save()
if self.finalGame:
self.shutdown()
return
self.waitForIntermission()
############################################################
# Method: __handleIntermission
# Purpose: This wrapper method tells the intermission to
# start.
# Input: task - a task that is spawned by a doMethodLater
# Output: None
############################################################
def __handleIntermission(self, task):
self.__startIntermission()
############################################################
# Method: getIntermissionTime
# Purpose: This method returns the time of when an
# intermission began. It is meant to provide a
# fairly accurate time countdown for the clients.
# Input: None
# Output: returns the timestamp of intermission start
############################################################
def getIntermissionTime(self):
return self.timeStamp
############################################################
# Method: __startIntermission
# Purpose: This method is responsible for starting the
# hourly intermission for bingo night.
# Input: None
# Output: None
############################################################
def __startIntermission(self):
for bingoMgr in list(self.doId2do.values()):
bingoMgr.setFinalGame(BG.INTERMISSION)
if not self.finalGame:
currentTime = time.localtime()
currentMin = currentTime[4]
currentSec = currentTime[5]
# Calculate time until the next hour
waitTime = (60-currentMin)*60 - currentSec
sec = (currentMin - BG.HOUR_BREAK_MIN)*60 + currentSec
self.timeStamp = globalClockDelta.getRealNetworkTime() - sec
self.notify.info('__startIntermission: Timestamp %s'%(self.timeStamp))
else:
# In case someone should decide that bingo night does not end on the hour, ie 30 past,
# then this will allow a five minute intermission to sync up the PBMgrAIs for the
# final game.
waitTime = BG.HOUR_BREAK_SESSION
self.timeStamp = globalClockDelta.getRealNetworkTime()
self.waitTaskName = 'waitForEndOfIntermission'
self.notify.info('__startIntermission: Waiting %s seconds until Bingo Night resumes.' %(waitTime))
taskMgr.doMethodLater(waitTime, self.__resumeBingoNight, self.waitTaskName)
return Task.done
############################################################
# Method: __waitForIntermission
# Purpose: This method is responsible for calculating the
# wait time for the hourly intermission for bingo
# night.
# Input: None
# Output: None
############################################################
def waitForIntermission(self):
currentTime = time.localtime()
currentMin = currentTime[4]
currentSec = currentTime[5]
# Calculate Amount of time needed for one normal game of Bingo from the
# Waitcountdown all the way to the gameover. (in secs)
if currentMin >= BG.HOUR_BREAK_MIN:
# If the AI starts during bingo night and after the intermission start(a crash or scheduled downtime),
# then immediately start the intermission to sync all the clients up for the next hour.
self.__startIntermission()
else:
waitTime = ((BG.HOUR_BREAK_MIN - currentMin)*60) - currentSec
self.waitTaskName = 'waitForIntermission'
self.notify.info("Waiting %s seconds until Final Game of the Hour should be announced." % (waitTime))
taskMgr.doMethodLater(waitTime, self.__handleIntermission, self.waitTaskName)
############################################################
# Method: generateBingoManagers
# Purpose: This method creates a PondBingoManager for each
# pond that is found within the hoods. It searches
# through each hood for pond objects and generates
# the corresponding ManagerAI objects.
# Input: None
# Output: None
############################################################
def generateBingoManagers(self):
# Create DPBMAI for all ponds in all hoods.
for hood in self.air.hoods:
self.createPondBingoMgrAI(hood)
# Create DPBMAI for every pond in every active estate.
for estateAI in list(self.air.estateMgr.estate.values()):
self.createPondBingoMgrAI(estateAI)
############################################################
# Method: addDistObj
# Purpose: This method adds the newly created Distributed
# object to the BingoManagerAI doId2do list for
# easy reference.
# Input: distObj
# Output: None
############################################################
def addDistObj(self, distObj):
self.notify.debug("addDistObj: Adding %s : %s" % (distObj.getDoId(), distObj.zoneId))
self.doId2do[distObj.getDoId()] = distObj
self.zoneId2do[distObj.zoneId] = distObj
def __hoodToUse(self, zoneId):
hood = ZoneUtil.getCanonicalHoodId(zoneId)
if hood >= TTG.DynamicZonesBegin:
hood = TTG.MyEstate
return hood
############################################################
# Method: createPondBingoMgrAI
# Purpose: This method generates PBMgrAI instances for
# each pond found in the specified hood. A hood
# may be an estate or an actual hood.
# Input: hood - HoodDataAI or EstateAI object.
# dynamic - Will be 1 only if an Estate was generated
# after Bingo Night has started.
# Output: None
############################################################
def createPondBingoMgrAI(self, hood, dynamic=0):
if hood.fishingPonds == None:
self.notify.warning("createPondBingoMgrAI: hood doesn't have any ponds... were they deleted? %s" % hood)
return
for pond in hood.fishingPonds:
# First, optain hood id based on zone id that the pond is located in.
hoodId = self.__hoodToUse(pond.zoneId)
if hoodId not in self.hood2doIdList:
# for now don't start it for minigolf zone and outdoor zone
continue
bingoMgr = DistributedPondBingoManagerAI.DistributedPondBingoManagerAI(self.air, pond)
bingoMgr.generateWithRequired(pond.zoneId)
self.addDistObj(bingoMgr)
if hasattr(hood, "addDistObj"):
hood.addDistObj(bingoMgr)
pond.setPondBingoManager(bingoMgr)
# Add the PBMgrAI reference to the hood2doIdList.
self.hood2doIdList[hoodId].append(bingoMgr.getDoId())
# Dynamic if this method was called when an estate was generated after
# Bingo Night has started.
if dynamic:
self.startDynPondBingoMgrAI(bingoMgr)
############################################################
# Method: startDynPondBingoMgrAI
# Purpose: This method determines what state a Dynamic
# Estate PBMgrAI should start in, and then it tells
# the PBMgrAI to start.
# Input: bingoMgr - PondBongoMgrAI Instance
# Output: None
############################################################
def startDynPondBingoMgrAI(self, bingoMgr):
currentMin = time.localtime()[4]
# If the dynamic estate is generated before the intermission starts
# and it is not the final game of the night, then the PBMgrAI should start
# in the WaitCountdown state. Otherwise, it should start in the intermission
# state so that it can sync up with all of the other Estate PBMgrAIs for the
# super bingo game.
initState = (((currentMin < BG.HOUR_BREAK_MIN) and (not self.finalGame)) and ['WaitCountdown'] or ['Intermission'])[0]
bingoMgr.startup(initState)
############################################################
# Method: removePondBingoMgrAI
# Purpose: This method generates PBMgrAI instances for
# each pond found in the specified hood. A hood
# may be an estate or an actual hood.
# Input: doId - the doId of the PBMgrAI that should be
# removed from the dictionaries.
# Output: None
############################################################
def removePondBingoMgrAI(self, doId):
if doId in self.doId2do:
zoneId = self.doId2do[doId].zoneId
self.notify.info('removePondBingoMgrAI: Removing PondBingoMgrAI %s' %(zoneId))
hood = self.__hoodToUse(zoneId)
self.hood2doIdList[hood].remove(doId)
del self.zoneId2do[zoneId]
del self.doId2do[doId]
else:
self.notify.debug('removeBingoManager: Attempt to remove invalid PondBingoManager %s' % (doId))
############################################################
# Method: SetFishForPlayer
# Purpose: This method adds the newly created Distributed
# object to the BingoManagerAI doId2do list for
# easy reference.
# Input: distObj
# Output: None
############################################################
def setAvCatchForPondMgr(self, avId, zoneId, catch):
self.notify.info('setAvCatchForPondMgr: zoneId %s' %(zoneId))
if zoneId in self.zoneId2do:
self.zoneId2do[zoneId].setAvCatch(avId, catch)
else:
self.notify.info('setAvCatchForPondMgr Failed: zoneId %s' %(zoneId))
############################################################
# Method: getFileName
# Purpose: This method constructs the jackpot filename for
# a particular shard.
# Input: None
# Output: returns jackpot filename
############################################################
def getFileName(self):
"""Figure out the path to the saved state"""
f = "%s%s.jackpot" % (self.serverDataFolder, self.shard)
return f
############################################################
# Method: saveTo
# Purpose: This method saves the current jackpot ammounts
# to the specified file.
# Input: file - file to save jackpot amounts
# Output: None
############################################################
def saveTo(self, file):
pickle.dump(self.__hoodJackpots, file)
############################################################
# Method: save
# Purpose: This method determines where to save the jackpot
# amounts.
# Input: None
# Output: None
############################################################
def save(self):
"""Save data to default location"""
try:
fileName = self.getFileName()
backup = fileName+ '.jbu'
if os.path.exists(fileName):
os.rename(fileName, backup)
file = open(fileName, 'wb')
file.seek(0)
self.saveTo(file)
file.close()
if os.path.exists(backup):
os.remove(backup)
except EnvironmentError:
self.notify.warning(str(sys.exc_info()[1]))
############################################################
# Method: loadFrom
# Purpose: This method loads the jackpot amounts from the
# specified file.
# Input: File - file to load amount from
# Output: returns a dictionary of the jackpots for this shard
############################################################
def loadFrom(self, file):
# Default Jackpot Amount
jackpots = self.DefaultReward
try:
jackpots = pickle.load(file)
except EOFError:
pass
return jackpots
############################################################
# Method: load
# Purpose: This method determines where to load the jackpot
# amounts.
# Input: None
# Output: None
############################################################
def load(self):
"""Load Jackpot data from default location"""
fileName = self.getFileName()
try:
file = open(fileName+'.jbu', 'rb')
if os.path.exists(fileName):
os.remove(fileName)
except IOError:
try:
file = open(fileName)
except IOError:
# Default Jackpot Amount
return self.DefaultReward
file.seek(0)
jackpots = self.loadFrom(file)
file.close()
return jackpots
############################################################
# Method: getSuperJackpot
# Purpose: This method returns the super jackpot amount for
# the specified zone. It calculates which hood
# the zone is in and returns the shared jackpot
# amount for that hood.
# Input: zoneId - retrieve jackpot for this zone's hood
# Output: returns jackpot for hood that zoneid is found in
############################################################
def getSuperJackpot(self, zoneId):
hood = self.__hoodToUse(zoneId)
self.notify.info('getSuperJackpot: hoodJackpots %s \t hood %s' % (self.__hoodJackpots, hood))
return self.__hoodJackpots.get(hood, [BG.MIN_SUPER_JACKPOT])[0]
############################################################
# Method: __startCloseEvent
# Purpose: This method starts to close Bingo Night down. One
# more super card game will be played at the end
# of the hour(unless the times are changed).
# Input: None
# Output: None
############################################################
def __startCloseEvent(self):
self.finalGame = BG.CLOSE_EVENT
if self.waitTaskName == 'waitForIntermission':
taskMgr.remove(self.waitTaskName)
self.__startIntermission()
############################################################
# Method: handleSuperBingoWin
# Purpose: This method handles a victory when a super
# bingo game has been one. It updates the jackpot
# amount and tells each of the other ponds in that
# hood that they did not win.
# Input: zoneId - pond who won the bingo game.
# Output: None
############################################################
def handleSuperBingoWin(self, zoneId):
# Reset the Jackpot and unmark the dirty bit.
hood = self.__hoodToUse(zoneId)
self.__hoodJackpots[hood][0] = self.DefaultReward[hood][0]
self.__hoodJackpots[hood][1] = 0
# tell everyone who won
#simbase.air.newsManager.bingoWin(zoneId)
# Tell the other ponds that they did not win and should handle the loss
for doId in self.hood2doIdList[hood]:
distObj = self.doId2do[doId]
if distObj.zoneId != zoneId:
self.notify.info("handleSuperBingoWin: Did not win in zone %s" %(distObj.zoneId))
distObj.handleSuperBingoLoss()

View file

@ -0,0 +1,89 @@
#################################################################
# class: BingoNightHolidayAI.py
#
# Purpose: Manages the Bingo Night Holiday for all ponds in all
# hoods.
# Note: The Holiday Manager System(HMS) deletes each Holiday AI
# instance when the particular Holiday Expires. Unfortunately,this
# sort of functionality is not ideal for the BingoManagerAI
# class because we want to allow players to finish a final
# game of bingo before the BingoManagerAI shuts down.
#
# In order to prevent the BingoManagerAI from shutting down
# unexpectantly in the middle of a game, we provide this
# class to act as a "buffer" between the HSM
# and the BingoManagerAI. This class is created
# and destroyed by the HMS. When the HMS tells this class
# to start, it instantiates a BingoManagerAI object to
# run the actual Bingo Night Holiday.
#
# When the stop call is received, it tells the BingoManagerAI
# to stop after the next game and then it removes the reference
# to the BingoManagerAI. A reference to the BingoManagerAI still
# remains in the AIR so that the BingoManagerAI can finish
# the final Bingo Night Games before it deletes itself.
#################################################################
#################################################################
# Direct Specific Modules
#################################################################
from direct.directnotify import DirectNotifyGlobal
from otp.otpbase import PythonUtil
from direct.task import Task
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.ai import HolidayBaseAI
from toontown.fishing import BingoGlobals
from toontown.fishing import BingoManagerAI
#################################################################
# Python Specific Modules
#################################################################
import time
class BingoNightHolidayAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory('BingoNightHolidayAI')
############################################################
# Method: __init__
# Purpose: This method initializes the HolidayBaseAI
# base class.
# Input: air - The AI Repository.
# Output: None
############################################################
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
############################################################
# Method: start
# Purpose: This method instantiates a BingoManagerAI and
# tells it to start up bingo night.
# Input: None
# Output: None
############################################################
def start(self):
if self.air.bingoMgr:
raise PythonUtil.SingletonError("Bingo Manager already Exists! DO NOT RUN HOLIDAY!!")
else:
self.notify.info('Starting BingoNight Holiday: %s' % (time.ctime()))
self.bingoMgr = BingoManagerAI.BingoManagerAI(self.air)
self.bingoMgr.start()
############################################################
# Method: start
# Purpose: This method tells the BingoManagerAI to shutdown
# and removes the reference. The BingoManagerAI
# does not actually shutdown until it finish all
# the PBMgrAIs have done so. The AIR maintains a
# reference to the BingoManagerAI so this method
# does not actually delete it.
# Input: None
# Output: None
############################################################
def stop(self):
if self.bingoMgr:
self.notify.info('stop: Tell the BingoManagerAI to stop BingoNight Holiday - %s' %(time.ctime()))
self.bingoMgr.stop()
del self.bingoMgr

View file

@ -0,0 +1,126 @@
from direct.directnotify import DirectNotifyGlobal
import random
from direct.task import Task
from . import DistributedFireworkShowAI
from toontown.ai import HolidayBaseAI
from . import FireworkShow
from toontown.toonbase.ToontownGlobals import DonaldsDock, ToontownCentral, \
TheBrrrgh, MinniesMelodyland, DaisyGardens, OutdoorZone, GoofySpeedway, DonaldsDreamland
import time
class FireworkManagerAI(HolidayBaseAI.HolidayBaseAI):
"""
Manages Fireworks holidays
"""
notify = DirectNotifyGlobal.directNotify.newCategory('FireworkManagerAI')
zoneToStyleDict = {
# Donald's Dock
DonaldsDock : 5,
# Toontown Central
ToontownCentral : 0,
# The Brrrgh
TheBrrrgh : 4,
# Minnie's Melodyland
MinniesMelodyland : 3,
# Daisy Gardens
DaisyGardens : 1,
# Acorn Acres
OutdoorZone : 0,
# GS
GoofySpeedway : 0,
# Donald's Dreamland
DonaldsDreamland : 2,
}
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
# Dict from zone to DistFireworkShow objects
self.fireworkShows = {}
self.waitTaskName = 'waitStartFireworkShows'
def start(self):
self.notify.info("Starting firework holiday: %s" % (time.ctime()))
self.waitForNextShow()
def stop(self):
self.notify.info("Stopping firework holiday: %s" % (time.ctime()))
taskMgr.remove(self.waitTaskName)
self.stopAllShows()
def startAllShows(self, task):
for hood in self.air.hoods:
showType = self.zoneToStyleDict.get(hood.canonicalHoodId)
if showType is not None:
self.startShow(hood.zoneId, showType)
self.waitForNextShow()
return Task.done
def waitForNextShow(self):
currentTime = time.localtime()
currentMin = currentTime[4]
currentSec = currentTime[5]
waitTime = ((60 - currentMin) * 60) - currentSec
self.notify.debug("Waiting %s seconds until next show" % (waitTime))
taskMgr.doMethodLater(waitTime, self.startAllShows, self.waitTaskName)
def startShow(self, zone, showType = -1, magicWord = 0):
"""
Start a show of showType in this zone.
Returns 1 if a show was successfully started.
Warns and returns 0 if a show was already running in this zone.
There can only be one show per zone.
"""
if zone in self.fireworkShows:
self.notify.warning("startShow: already running a show in zone: %s" % (zone))
return 0
self.notify.debug("startShow: zone: %s showType: %s" % (zone, showType))
# Create a show, passing ourselves in so it can tell us when
# the show is over
show = DistributedFireworkShowAI.DistributedFireworkShowAI(self.air, self)
show.generateWithRequired(zone)
self.fireworkShows[zone] = show
# Currently needed to support legacy fireworks
if simbase.air.config.GetBool('want-old-fireworks', 0) or magicWord == 1:
show.d_startShow(showType, showType)
else:
show.d_startShow(self.holidayId, showType)
# Success!
return 1
def stopShow(self, zone):
"""
Stop a firework show in this zone.
Returns 1 if it did stop a show, warns and returns 0 if there is not one
"""
if zone not in self.fireworkShows:
self.notify.warning("stopShow: no show running in zone: %s" % (zone))
return 0
self.notify.debug("stopShow: zone: %s" % (zone))
show = self.fireworkShows[zone]
del self.fireworkShows[zone]
show.requestDelete()
# Success!
return 1
def stopAllShows(self):
"""
Stop all firework shows this manager knows about in all zones.
Returns number of shows stopped by this command.
"""
numStopped = 0
for zone, show in list(self.fireworkShows.items()):
self.notify.debug("stopAllShows: zone: %s" % (zone))
show.requestDelete()
numStopped += 1
self.fireworkShows.clear()
return numStopped
def isShowRunning(self, zone):
"""
Is there currently a show running in this zone?
"""
return zone in self.fireworkShows

View file

@ -19,7 +19,6 @@ class ClosetGUI(ClothesGUI.ClothesGUI):
self.isOwner = isOwner self.isOwner = isOwner
self.deleteEvent = deleteEvent self.deleteEvent = deleteEvent
self.cancelEvent = cancelEvent self.cancelEvent = cancelEvent
self.genderChange = 0
self.verify = None self.verify = None
return return
@ -75,7 +74,6 @@ class ClosetGUI(ClothesGUI.ClothesGUI):
def setupScrollInterface(self): def setupScrollInterface(self):
self.notify.debug('setupScrollInterface') self.notify.debug('setupScrollInterface')
self.dna = self.toon.getStyle() self.dna = self.toon.getStyle()
self.gender = self.dna.getGender()
self.swappedTorso = 0 self.swappedTorso = 0
if self.topsList == None: if self.topsList == None:
self.topsList = self.toon.getClothesTopsList() self.topsList = self.toon.getClothesTopsList()
@ -125,9 +123,6 @@ class ClosetGUI(ClothesGUI.ClothesGUI):
else: else:
self.bottomTrashButton['text'] = TTLocalizer.ClosetDeleteShorts self.bottomTrashButton['text'] = TTLocalizer.ClosetDeleteShorts
def setGender(self, gender):
self.ownerGender = gender
self.genderChange = 1
def swapBottom(self, offset): def swapBottom(self, offset):
length = len(self.bottoms) length = len(self.bottoms)
@ -140,11 +135,6 @@ class ClosetGUI(ClothesGUI.ClothesGUI):
return None return None
self.toon.style.botTex = self.bottoms[self.bottomChoice][0] self.toon.style.botTex = self.bottoms[self.bottomChoice][0]
self.toon.style.botTexColor = self.bottoms[self.bottomChoice][1] self.toon.style.botTexColor = self.bottoms[self.bottomChoice][1]
if self.genderChange == 1:
if self.bottomChoice > 0:
self.__handleGenderBender(1)
else:
self.__handleGenderBender(0)
if self.toon.generateToonClothes() == 1: if self.toon.generateToonClothes() == 1:
self.toon.loop('neutral', 0) self.toon.loop('neutral', 0)
self.swappedTorso = 1 self.swappedTorso = 1
@ -153,20 +143,6 @@ class ClosetGUI(ClothesGUI.ClothesGUI):
if self.swapEvent != None: if self.swapEvent != None:
messenger.send(self.swapEvent) messenger.send(self.swapEvent)
def __handleGenderBender(self, type):
if type == 1:
if self.toon.style.gender != self.ownerGender and self.toon.style.gender == 'f':
self.toon.swapToonTorso(self.toon.style.torso[0] + 's', genClothes=0)
self.toon.loop('neutral', 0)
self.swappedTorso = 1
self.toon.style.gender = self.ownerGender
else:
self.toon.style.gender = self.gender
if self.toon.style.gender != self.ownerGender and self.toon.style.gender == 'm':
self.toon.swapToonTorso(self.toon.style.torso[0] + 's', genClothes=0)
self.toon.loop('neutral', 0)
self.swappedTorso = 1
def removeTop(self, index): def removeTop(self, index):
listLen = len(self.tops) listLen = len(self.tops)
if index < listLen: if index < listLen:

View file

@ -46,7 +46,6 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
self.purchaseDoneEvent = '' self.purchaseDoneEvent = ''
self.swapEvent = '' self.swapEvent = ''
self.locked = 0 self.locked = 0
self.gender = None
self.topDeleted = 0 self.topDeleted = 0
self.bottomDeleted = 0 self.bottomDeleted = 0
self.closetTrack = None self.closetTrack = None
@ -130,7 +129,6 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
self.popupInfo = None self.popupInfo = None
if self.av: if self.av:
del self.av del self.av
del self.gender
del self.closetSphere del self.closetSphere
del self.closetSphereNode del self.closetSphereNode
del self.closetSphereNodePath del self.closetSphereNodePath
@ -200,10 +198,9 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
self.hasLocalAvatar = 1 self.hasLocalAvatar = 1
return return
def setState(self, mode, avId, ownerId, gender, topList, botList): def setState(self, mode, avId, ownerId, topList, botList):
self.notify.debug('setState, mode=%s, avId=%s, ownerId=%d' % (mode, avId, ownerId)) self.notify.debug('setState, mode=%s, avId=%s, ownerId=%d' % (mode, avId, ownerId))
self.isOwner = avId == ownerId self.isOwner = avId == ownerId
self.ownerGender = gender
if mode == ClosetGlobals.CLOSED: if mode == ClosetGlobals.CLOSED:
self.fsm.request('closed') self.fsm.request('closed')
return return
@ -212,7 +209,6 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
self.av = self.cr.doId2do.get(self.customerId, None) self.av = self.cr.doId2do.get(self.customerId, None)
if self.av: if self.av:
if base.localAvatar.getDoId() == self.customerId: if base.localAvatar.getDoId() == self.customerId:
self.gender = self.av.style.gender
self.topList = topList self.topList = topList
self.botList = botList self.botList = botList
self.oldTopList = self.topList[0:] self.oldTopList = self.topList[0:]
@ -227,10 +223,6 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
self.fsm.request('open') self.fsm.request('open')
return return
def _revertGender(self):
if self.gender:
self.av.style.gender = self.gender
self.av.loop('neutral')
def popupChangeClothesGUI(self, task): def popupChangeClothesGUI(self, task):
self.notify.debug('popupChangeClothesGUI') self.notify.debug('popupChangeClothesGUI')
@ -246,8 +238,6 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
if not self.closetGUI: if not self.closetGUI:
self.closetGUI = ClosetGUI.ClosetGUI(self.isOwner, self.purchaseDoneEvent, self.cancelEvent, self.swapEvent, self.deleteEvent, self.topList, self.botList) self.closetGUI = ClosetGUI.ClosetGUI(self.isOwner, self.purchaseDoneEvent, self.cancelEvent, self.swapEvent, self.deleteEvent, self.topList, self.botList)
self.closetGUI.load() self.closetGUI.load()
if self.gender != self.ownerGender:
self.closetGUI.setGender(self.ownerGender)
self.closetGUI.enter(base.localAvatar) self.closetGUI.enter(base.localAvatar)
self.closetGUI.showButtons() self.closetGUI.showButtons()
style = self.av.getStyle() style = self.av.getStyle()
@ -374,7 +364,7 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
return return
def printInfo(self): def printInfo(self):
print('avid: %s, gender: %s' % (self.av.doId, self.av.style.gender)) print('avid: %s' % (self.av.doId))
print('current top = %s,%s,%s,%s and bot = %s,%s,' % (self.av.style.topTex, print('current top = %s,%s,%s,%s and bot = %s,%s,' % (self.av.style.topTex,
self.av.style.topTexColor, self.av.style.topTexColor,
self.av.style.sleeveTex, self.av.style.sleeveTex,
@ -393,7 +383,6 @@ class DistributedCloset(DistributedFurnitureItem.DistributedFurnitureItem):
return return
elif mode == ClosetGlobals.CLOSET_MOVIE_COMPLETE: elif mode == ClosetGlobals.CLOSET_MOVIE_COMPLETE:
if self.isLocalToon: if self.isLocalToon:
self._revertGender()
print('-----------ending trunk interaction-----------') print('-----------ending trunk interaction-----------')
self.printInfo() self.printInfo()
print('-------------------------------------------------') print('-------------------------------------------------')

View file

@ -22,14 +22,15 @@ def dnaCodeFromToonDNA(dna):
return i return i
if dna.gender == 'f':
genderTypeNum = 0 if dna.eyelashes == 0:
eyelashesTypeNum = 0
else: else:
genderTypeNum = 1 eyelashesTypeNum = 1
legTypeNum = findItemNumInList(dna.legs, ToonDNA.toonLegTypes) << 1 legTypeNum = findItemNumInList(dna.legs, ToonDNA.toonLegTypes) << 1
torsoTypeNum = findItemNumInList(dna.torso, ToonDNA.toonTorsoTypes) << 3 torsoTypeNum = findItemNumInList(dna.torso, ToonDNA.toonTorsoTypes) << 3
headTypeNum = findItemNumInList(dna.head, ToonDNA.toonHeadTypes) << 7 headTypeNum = findItemNumInList(dna.head, ToonDNA.toonHeadTypes) << 7
return headTypeNum | torsoTypeNum | legTypeNum | genderTypeNum return headTypeNum | torsoTypeNum | legTypeNum | eyelashesTypeNum
class DistributedToonStatuary(DistributedStatuary.DistributedStatuary): class DistributedToonStatuary(DistributedStatuary.DistributedStatuary):
@ -46,7 +47,7 @@ class DistributedToonStatuary(DistributedStatuary.DistributedStatuary):
self.model.setScale(self.worldScale * 1.5, self.worldScale * 1.5, self.worldScale) self.model.setScale(self.worldScale * 1.5, self.worldScale * 1.5, self.worldScale)
self.getToonPropertiesFromOptional() self.getToonPropertiesFromOptional()
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToonFromProperties(self.headType, self.torsoType, self.legType, self.gender, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) dna.newToonFromProperties(self.headType, self.torsoType, self.legType, self.eyelashes, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
self.setupStoneToon(dna) self.setupStoneToon(dna)
self.poseToonFromTypeIndex(self.typeIndex) self.poseToonFromTypeIndex(self.typeIndex)
self.toon.reparentTo(self.model) self.toon.reparentTo(self.model)
@ -62,7 +63,7 @@ class DistributedToonStatuary(DistributedStatuary.DistributedStatuary):
self.toon.initializeBodyCollisions('toonStatue') self.toon.initializeBodyCollisions('toonStatue')
self.toon.stopBlink() self.toon.stopBlink()
self.toon.stopLookAround() self.toon.stopLookAround()
self.gender = self.toon.style.gender self.eyelashes = self.toon.style.eyelashes
self.speciesType = self.toon.style.getAnimal() self.speciesType = self.toon.style.getAnimal()
self.headType = self.toon.style.head self.headType = self.toon.style.head
self.removeTextures() self.removeTextures()
@ -85,7 +86,7 @@ class DistributedToonStatuary(DistributedStatuary.DistributedStatuary):
self.toon.pose('victory', 30) self.toon.pose('victory', 30)
self.toon.setH(180) self.toon.setH(180)
self.speciesType = self.toon.style.getAnimal() self.speciesType = self.toon.style.getAnimal()
self.gender = self.toon.style.gender self.eyelashes = self.toon.style.eyelashes
def setupCollision(self): def setupCollision(self):
DistributedStatuary.DistributedStatuary.setupCollision(self) DistributedStatuary.DistributedStatuary.setupCollision(self)
@ -164,7 +165,7 @@ class DistributedToonStatuary(DistributedStatuary.DistributedStatuary):
tsLashes = TextureStage('tsLashes') tsLashes = TextureStage('tsLashes')
tsLashes.setPriority(2) tsLashes.setPriority(2)
tsLashes.setMode(tsLashes.MDecal) tsLashes.setMode(tsLashes.MDecal)
if self.gender == 'f': if self.eyelashes:
if self.toon.hasLOD(): if self.toon.hasLOD():
head = self.toon.getPart('head', '1000') head = self.toon.getPart('head', '1000')
else: else:
@ -188,14 +189,14 @@ class DistributedToonStatuary(DistributedStatuary.DistributedStatuary):
self.optional = optional self.optional = optional
def getToonPropertiesFromOptional(self): def getToonPropertiesFromOptional(self):
genderTypeNum = self.optional & 1 eyelashesTypeNum = self.optional & 1
legTypeNum = (self.optional & 6) >> 1 legTypeNum = (self.optional & 6) >> 1
torsoTypeNum = (self.optional & 120) >> 3 torsoTypeNum = (self.optional & 120) >> 3
headTypeNum = (self.optional & 65408) >> 7 headTypeNum = (self.optional & 65408) >> 7
if genderTypeNum == 0: if eyelashesTypeNum == 0:
self.gender = 'f' self.eyelashes = 0
else: else:
self.gender = 'm' self.eyelashes = 1
if legTypeNum <= len(ToonDNA.toonLegTypes): if legTypeNum <= len(ToonDNA.toonLegTypes):
self.legType = ToonDNA.toonLegTypes[legTypeNum] self.legType = ToonDNA.toonLegTypes[legTypeNum]
if torsoTypeNum <= len(ToonDNA.toonTorsoTypes): if torsoTypeNum <= len(ToonDNA.toonTorsoTypes):

View file

@ -34,7 +34,7 @@ class DistributedTrunk(DistributedCloset.DistributedCloset):
self.isFreePlayer = 0 self.isFreePlayer = 0
def printInfo(self): def printInfo(self):
print('avid: %s, gender: %s' % (self.av.doId, self.av.style.gender)) print('avid: %s, ' % (self.av.doId))
print('current hat = %s, glasses = %s, backpack = %s, shoes = %s' % (self.av.getHat(), print('current hat = %s, glasses = %s, backpack = %s, shoes = %s' % (self.av.getHat(),
self.av.getGlasses(), self.av.getGlasses(),
self.av.getBackpack(), self.av.getBackpack(),
@ -44,10 +44,9 @@ class DistributedTrunk(DistributedCloset.DistributedCloset):
print('backpackList = %s' % self.av.getBackpackList()) print('backpackList = %s' % self.av.getBackpackList())
print('shoesList = %s' % self.av.getShoesList()) print('shoesList = %s' % self.av.getShoesList())
def setState(self, mode, avId, ownerId, gender, hatList, glassesList, backpackList, shoesList): def setState(self, mode, avId, ownerId, hatList, glassesList, backpackList, shoesList):
self.notify.debug('setState, mode=%s, avId=%s, ownerId=%d' % (mode, avId, ownerId)) self.notify.debug('setState, mode=%s, avId=%s, ownerId=%d' % (mode, avId, ownerId))
self.isOwner = avId == ownerId self.isOwner = avId == ownerId
self.ownerGender = gender
if mode == ClosetGlobals.CLOSED: if mode == ClosetGlobals.CLOSED:
self.fsm.request('closed') self.fsm.request('closed')
return return
@ -61,7 +60,7 @@ class DistributedTrunk(DistributedCloset.DistributedCloset):
else: else:
self.isFreePlayer = 0 self.isFreePlayer = 0
if base.localAvatar.getDoId() == self.customerId: if base.localAvatar.getDoId() == self.customerId:
self.gender = self.av.style.gender self.eyelashes = self.av.style.eyelashes
self.hatList = hatList self.hatList = hatList
self.glassesList = glassesList self.glassesList = glassesList
self.backpackList = backpackList self.backpackList = backpackList
@ -112,8 +111,6 @@ class DistributedTrunk(DistributedCloset.DistributedCloset):
if not self.closetGUI: if not self.closetGUI:
self.closetGUI = TrunkGUI.TrunkGUI(self.isOwner, self.purchaseDoneEvent, self.cancelEvent, self.swapHatEvent, self.swapGlassesEvent, self.swapBackpackEvent, self.swapShoesEvent, self.deleteEvent, self.hatList, self.glassesList, self.backpackList, self.shoesList) self.closetGUI = TrunkGUI.TrunkGUI(self.isOwner, self.purchaseDoneEvent, self.cancelEvent, self.swapHatEvent, self.swapGlassesEvent, self.swapBackpackEvent, self.swapShoesEvent, self.deleteEvent, self.hatList, self.glassesList, self.backpackList, self.shoesList)
self.closetGUI.load() self.closetGUI.load()
if self.gender != self.ownerGender:
self.closetGUI.setGender(self.ownerGender)
self.closetGUI.enter(base.localAvatar) self.closetGUI.enter(base.localAvatar)
self.closetGUI.showButtons() self.closetGUI.showButtons()
oldHat = self.av.getHat() oldHat = self.av.getHat()

View file

@ -1,649 +1,5 @@
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.distributed.DistributedObjectAI import DistributedObjectAI
import functools
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from direct.fsm.FSM import FSM
from toontown.estate import HouseGlobals
from toontown.estate.DistributedHouseAI import DistributedHouseAI
from toontown.toon import ToonDNA
class LoadHouseOperation(FSM):
def __init__(self, mgr, estate, index, avatar, callback):
FSM.__init__(self, 'LoadHouseOperation')
self.mgr = mgr
self.estate = estate
self.index = index
self.avatar = avatar
self.callback = callback
self.done = False
self.houseId = None
self.house = None
self.gender = None
def start(self):
# We have a few different cases here:
if self.avatar is None:
# Case #1: There isn't an avatar in that estate slot. Make a blank house.
# Because this state completes so fast, we'll use taskMgr to delay
# it until the next iteration. This solves reentrancy problems.
taskMgr.doMethodLater(0.0, self.demand, 'makeBlankHouse-%s' % id(self), extraArgs=['MakeBlankHouse'])
return
style = ToonDNA.ToonDNA()
style.makeFromNetString(self.avatar.get('setDNAString')[0])
self.houseId = self.avatar.get('setHouseId', [0])[0]
self.gender = style.gender
if self.houseId == 0:
# Case #2: There is an avatar, but no setHouseId. Make a new house:
self.demand('CreateHouse')
else:
# Case #3: Avatar with a setHouseId. Load it:
self.demand('LoadHouse')
def enterMakeBlankHouse(self):
self.house = DistributedHouseAI(self.mgr.air)
self.house.setHousePos(self.index)
self.house.setColor(self.index)
self.house.generateWithRequired(self.estate.zoneId)
self.estate.houses[self.index] = self.house
self.demand('Off')
def enterCreateHouse(self):
self.mgr.air.dbInterface.createObject(self.mgr.air.dbId, self.mgr.air.dclassesByName['DistributedHouseAI'],
{'setName': [self.avatar['setName'][0]],
'setAvatarId': [self.avatar['avId']]}, self.__handleHouseCreated)
def __handleHouseCreated(self, houseId):
if self.state != 'CreateHouse':
# This operation was likely aborted.
return
# Update the avatar's houseId:
av = self.mgr.air.doId2do.get(self.avatar['avId'])
if av:
av.b_setHouseId(houseId)
else:
self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.avatar['avId'],
self.mgr.air.dclassesByName['DistributedToonAI'],
{'setHouseId': [houseId]})
self.houseId = houseId
self.demand('LoadHouse')
def enterLoadHouse(self):
# Activate the house:
self.mgr.air.sendActivate(self.houseId, self.mgr.air.districtId, self.estate.zoneId,
self.mgr.air.dclassesByName['DistributedHouseAI'],
{'setHousePos': [self.index],
'setColor': [self.index],
'setName': [self.avatar['setName'][0]],
'setAvatarId': [self.avatar['avId']]})
# Wait for the house to generate:
self.acceptOnce('generate-%d' % self.houseId, self.__handleHouseGenerated)
def __handleHouseGenerated(self, house):
# The house will need to be able to reference
# the estate for setting up gardens, so:
house.estate = self.estate
# Initialize our interior:
house.interior.gender = self.gender
house.interior.start()
self.house = house
self.estate.houses[self.index] = self.house
if config.GetBool('want-gardening', False):
# Initialize our garden:
self.house.createGardenManager()
self.demand('Off')
def exitLoadHouse(self):
self.ignore('generate-%d' % self.houseId)
def enterOff(self):
self.done = True
self.callback(self.house)
class LoadEstateOperation(FSM):
def __init__(self, mgr, callback):
FSM.__init__(self, 'LoadEstateOperation')
self.mgr = mgr
self.callback = callback
self.estate = None
self.accId = None
self.zoneId = None
self.avIds = None
self.avatars = None
self.houseOperations = None
self.petOperations = None
def start(self, accId, zoneId):
self.accId = accId
self.zoneId = zoneId
self.demand('QueryAccount')
def enterQueryAccount(self):
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, self.accId, self.__handleQueryAccount)
def __handleQueryAccount(self, dclass, fields):
if self.state != 'QueryAccount':
# This operation was likely aborted.
return
if dclass != self.mgr.air.dclassesByName['AccountAI']:
self.mgr.notify.warning('Account %d has non-account dclass %d!' % (self.accId, dclass))
self.demand('Failure')
return
self.accFields = fields
self.estateId = fields.get('ESTATE_ID', 0)
self.demand('QueryAvatars')
def enterQueryAvatars(self):
self.avIds = self.accFields.get('ACCOUNT_AV_SET', [0] * 6)
self.avatars = {}
for index, avId in enumerate(self.avIds):
if avId == 0:
self.avatars[index] = None
continue
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, avId,
functools.partial(self.__handleQueryAvatar, index=index))
def __handleQueryAvatar(self, dclass, fields, index):
if self.state != 'QueryAvatars':
# This operation was likely aborted.
return
if dclass != self.mgr.air.dclassesByName['DistributedToonAI']:
self.mgr.notify.warning(
'Account %d has avatar %d with non-Toon dclass %d!' % (self.accId, self.avIds[index], dclass))
self.demand('Failure')
return
fields['avId'] = self.avIds[index]
self.avatars[index] = fields
if len(self.avatars) == 6:
self.__gotAllAvatars()
def __gotAllAvatars(self):
# We have all of our avatars, so now we can handle the estate.
if self.estateId:
# We already have an estate, so let's load that:
self.demand('LoadEstate')
else:
# We don't yet have an estate, so let's make one:
self.demand('CreateEstate')
def enterCreateEstate(self):
# Create a blank estate object:
self.mgr.air.dbInterface.createObject(self.mgr.air.dbId, self.mgr.air.dclassesByName['DistributedEstateAI'], {},
self.__handleEstateCreated)
def __handleEstateCreated(self, estateId):
if self.state != 'CreateEstate':
# This operation was likely aborted.
return
self.estateId = estateId
# Store the new estate object on our account:
self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.accId, self.mgr.air.dclassesByName['AccountAI'],
{'ESTATE_ID': estateId})
self.demand('LoadEstate')
def enterLoadEstate(self):
# Set the estate fields:
fields = {'setSlot%dToonId' % i: (avId,) for i, avId in enumerate(self.avIds)}
# Activate the estate:
self.mgr.air.sendActivate(self.estateId, self.mgr.air.districtId, self.zoneId,
self.mgr.air.dclassesByName['DistributedEstateAI'], fields)
# Wait for the estate to generate:
self.acceptOnce('generate-%d' % self.estateId, self.__handleEstateGenerated)
def __handleEstateGenerated(self, estate):
# Get the estate:
self.estate = estate
# For keeping track of pets in this estate:
self.estate.pets = []
# Map the owner to the estate:
ownerId = self.mgr.getOwnerFromZone(self.estate.zoneId)
owner = self.mgr.air.doId2do.get(ownerId)
if owner:
self.mgr.toon2estate[owner] = self.estate
# Set the estate's ID list:
self.estate.b_setIdList(self.avIds)
# Load houses:
self.demand('LoadHouses')
def exitLoadEstate(self):
self.ignore('generate-%d' % self.estateId)
def enterLoadHouses(self):
self.houseOperations = []
for houseIndex in xrange(6):
houseOperation = LoadHouseOperation(self.mgr, self.estate, houseIndex, self.avatars[houseIndex],
self.__handleHouseLoaded)
self.houseOperations.append(houseOperation)
houseOperation.start()
def __handleHouseLoaded(self, house):
if self.state != 'LoadHouses':
# We aren't loading houses, so we probably got cancelled. Therefore,
# the only sensible thing to do is simply destroy the house.
house.requestDelete()
return
# A house operation just finished! Let's see if all of them are done:
if all(houseOperation.done for houseOperation in self.houseOperations):
# Load our pets:
self.demand('LoadPets')
def enterLoadPets(self):
self.petOperations = []
for houseIndex in xrange(6):
av = self.avatars[houseIndex]
if av and av['setPetId'][0] != 0:
petOperation = LoadPetOperation(self.mgr, self.estate, av, self.__handlePetLoaded)
self.petOperations.append(petOperation)
petOperation.start()
if not self.petOperations:
taskMgr.doMethodLater(0, lambda: self.demand('Finished'), 'no-pets', extraArgs=[])
def __handlePetLoaded(self, pet):
if self.state != 'LoadPets':
pet.requestDelete()
return
# A pet operation just finished! Let's see if all of them are done:
if all(petOperation.done for petOperation in self.petOperations):
self.demand('Finished')
def enterFinished(self):
self.petOperations = []
self.callback(True)
def enterFailure(self):
self.cancel()
self.callback(False)
def cancel(self):
if self.estate:
self.estate.destroy()
self.estate = None
self.demand('Off')
class LoadPetOperation(FSM):
def __init__(self, mgr, estate, toon, callback):
FSM.__init__(self, 'LoadPetFSM')
self.mgr = mgr
self.estate = estate
self.toon = toon
self.callback = callback
self.done = False
self.petId = 0
def start(self):
if type(self.toon) == dict:
self.petId = self.toon['setPetId'][0]
else:
self.petId = self.toon.getPetId()
if self.petId not in self.mgr.air.doId2do:
self.mgr.air.sendActivate(self.petId, self.mgr.air.districtId, self.estate.zoneId)
self.acceptOnce('generate-%d' % self.petId, self.__generated)
else:
self.__generated(self.mgr.air.doId2do[self.petId])
def __generated(self, pet):
self.pet = pet
self.estate.pets.append(pet)
self.demand('Off')
def enterOff(self):
self.ignore('generate-%d' % self.petId)
self.done = True
self.callback(self.pet)
class EstateManagerAI(DistributedObjectAI): class EstateManagerAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('EstateManagerAI') notify = DirectNotifyGlobal.directNotify.newCategory('EstateManagerAI')
def __init__(self, air):
DistributedObjectAI.__init__(self, air)
self.toon2estate = {}
self.estate = {}
self.estate2toons = {}
self.estate2timeout = {}
self.zone2toons = {}
self.zone2owner = {}
self.petOperations = []
def getEstateZone(self, avId, name):
# Thank you name, very cool!
senderId = self.air.getAvatarIdFromSender()
accId = self.air.getAccountIdFromSender()
senderAv = self.air.doId2do.get(senderId)
if not senderAv:
self.air.writeServerEvent('suspicious', senderId, 'Sent getEstateZone() but not on district!')
return
# If an avId has been provided, then the sender wants to visit a friend.
# In this case, we do not need to load the estate, we only need to check
# to see if it already exists.
if avId and avId != senderId:
av = self.air.doId2do.get(avId)
if av and av.dclass == self.air.dclassesByName['DistributedToonAI']:
estate = self.toon2estate.get(av)
if estate:
# Found an estate!
avId = estate.owner.doId
zoneId = estate.zoneId
self._mapToEstate(senderAv, estate)
# In case the sender is teleporting from their estate
# to another estate, we want to unload their estate.
self._unloadEstate(senderAv)
if senderAv and senderAv.getPetId() != 0:
pet = self.air.doId2do.get(senderAv.getPetId())
if pet:
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadPet,
extraArgs=[estate, senderAv])
pet.requestDelete()
else:
self.__handleLoadPet(estate, senderAv)
# Now we want to send the sender to the estate.
if hasattr(senderAv, 'enterEstate'):
senderAv.enterEstate(avId, zoneId)
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [avId, zoneId])
# We weren't able to find the given avId at an estate, that's pretty sad.
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0])
return
# Otherwise, the sender wants to go to their own estate.
estate = getattr(senderAv, 'estate', None)
if estate:
# The sender already has an estate loaded, so let's send them there.
self._mapToEstate(senderAv, senderAv.estate)
if senderAv and senderAv.getPetId() != 0:
pet = self.air.doId2do.get(senderAv.getPetId())
if pet:
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadPet,
extraArgs=[estate, senderAv])
pet.requestDelete()
else:
self.__handleLoadPet(estate, senderAv)
if hasattr(senderAv, 'enterEstate'):
senderAv.enterEstate(senderId, estate.zoneId)
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, estate.zoneId])
# If a timeout is active, cancel it:
if estate in self.estate2timeout:
self.estate2timeout[estate].remove()
del self.estate2timeout[estate]
return
if getattr(senderAv, 'loadEstateOperation', None):
# We already have a loading operation underway; ignore this second
# request since the first operation will setEstateZone() when it
# finishes anyway.
return
zoneId = self.air.allocateZone()
self.zone2owner[zoneId] = avId
def estateLoaded(success):
if success:
senderAv.estate = senderAv.loadEstateOperation.estate
senderAv.estate.owner = senderAv
self._mapToEstate(senderAv, senderAv.estate)
if hasattr(senderAv, 'enterEstate'):
senderAv.enterEstate(senderId, zoneId)
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, zoneId])
else:
# Estate loading failed. Sad!
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0])
# Might as well free up the zoneId as well.
self.air.deallocateZone(zoneId)
del self.zone2owner[zoneId]
senderAv.loadEstateOperation = None
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.doId), self.__handleUnexpectedExit, extraArgs=[senderAv])
if senderAv and senderAv.getPetId() != 0:
pet = self.air.doId2do.get(senderAv.getPetId())
if pet:
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadEstate,
extraArgs=[senderAv, estateLoaded, accId, zoneId])
pet.requestDelete()
return
self.__handleLoadEstate(senderAv, estateLoaded, accId, zoneId)
def __handleUnexpectedExit(self, senderAv):
self._unmapFromEstate(senderAv)
self._unloadEstate(senderAv)
def exitEstate(self):
senderId = self.air.getAvatarIdFromSender()
senderAv = self.air.doId2do.get(senderId)
if not senderAv:
self.air.writeServerEvent('suspicious', senderId, 'Sent exitEstate() but not on district!')
return
self._unmapFromEstate(senderAv)
self._unloadEstate(senderAv)
def removeFriend(self, ownerId, avId):
if not (ownerId or avId):
return
owner = self.air.doId2do.get(ownerId)
if not owner:
return
friend = self.air.doId2do.get(avId)
if not friend:
return
estate = self.estate.get(ownerId)
if not estate:
return
if ownerId not in estate.getIdList():
return
toons = self.estate2toons.get(estate, [])
if owner not in toons and friend not in toons:
return
friendInList = False
for friendPair in owner.getFriendsList():
if type(friendPair) == tuple:
friendId = friendPair[0]
else:
friendId = friendPair
if friendId == avId:
friendInList = True
break
if not friendInList:
self.sendUpdateToAvatarId(friend.doId, 'sendAvToPlayground', [friend.doId, 1])
def _unloadEstate(self, av):
if getattr(av, 'estate', None):
estate = av.estate
if estate not in self.estate2timeout:
self.estate2timeout[estate] = taskMgr.doMethodLater(HouseGlobals.BOOT_GRACE_PERIOD, self._cleanupEstate,
estate.uniqueName('unload-estate'),
extraArgs=[estate])
# Send warning:
self._sendToonsToPlayground(av.estate, 0)
if getattr(av, 'loadEstateOperation', None):
self.air.deallocateZone(av.loadEstateOperation.zoneId)
av.loadEstateOperation.cancel()
av.loadEstateOperation = None
if av and hasattr(av, 'exitEstate') and hasattr(av, 'isInEstate') and av.isInEstate():
av.exitEstate()
if av and av.getPetId() != 0:
self.ignore(self.air.getAvatarExitEvent(av.getPetId()))
pet = self.air.doId2do.get(av.getPetId())
if pet:
pet.requestDelete()
self.ignore(self.air.getAvatarExitEvent(av.doId))
def _mapToEstate(self, av, estate):
self._unmapFromEstate(av)
self.estate[av.doId] = estate
self.estate2toons.setdefault(estate, []).append(av)
if av not in self.toon2estate:
self.toon2estate[av] = estate
self.zone2toons.setdefault(estate.zoneId, []).append(av.doId)
def _unmapFromEstate(self, av):
estate = self.toon2estate.get(av)
if not estate:
return
try:
del self.estate[av.doId]
except KeyError:
pass
del self.toon2estate[av]
try:
self.estate2toons[estate].remove(av)
except (KeyError, ValueError):
pass
try:
self.zone2toons[estate.zoneId].remove(av.doId)
except (KeyError, ValueError):
pass
def _cleanupEstate(self, estate):
# Boot all avatars from estate:
self._sendToonsToPlayground(estate, 1)
# Clean up avatar <-> estate mappings:
for av in self.estate2toons.get(estate, []):
try:
del self.estate[av.doId]
del self.toon2estate[av]
except KeyError:
pass
try:
del self.estate2toons[estate]
except KeyError:
pass
try:
del self.zone2toons[estate.zoneId]
except KeyError:
pass
# Clean up timeout, if it exists:
if estate in self.estate2timeout:
del self.estate2timeout[estate]
# Destroy estate and unmap from owner:
estate.destroy()
estate.owner.estate = None
# Destroy pets:
for pet in estate.pets:
pet.requestDelete()
estate.pets = []
# Free estate's zone:
self.air.deallocateZone(estate.zoneId)
del self.zone2owner[estate.zoneId]
def _sendToonsToPlayground(self, estate, reason):
for toon in self.estate2toons.get(estate, []):
self.sendUpdateToAvatarId(toon.doId, 'sendAvToPlayground', [toon.doId, reason])
def getEstateZones(self, ownerId):
toon = self.air.doId2do.get(ownerId)
if not toon:
return []
estate = self.toon2estate.get(toon)
if not estate:
return []
return [estate.zoneId]
def getEstateHouseZones(self, ownerId):
houseZones = []
toon = self.air.doId2do.get(ownerId)
if not toon:
return houseZones
estate = self.toon2estate.get(toon)
if not estate:
return houseZones
houses = estate.houses
for house in houses:
houseZones.append(house.interiorZone)
return houseZones
def _lookupEstate(self, toon):
return self.toon2estate.get(toon)
def getOwnerFromZone(self, zoneId):
return self.zone2owner.get(zoneId, 0)
def __handleLoadPet(self, estate, av):
petOperation = LoadPetOperation(self, estate, av, self.__handlePetLoaded)
self.petOperations.append(petOperation)
petOperation.start()
def __handlePetLoaded(self, _):
# A pet operation just finished! Let's see if all of them are done:
if all(petOperation.done for petOperation in self.petOperations):
self.petOperations = []
def __handleLoadEstate(self, av, callback, accId, zoneId):
self._unmapFromEstate(av)
av.loadEstateOperation = LoadEstateOperation(self, callback)
av.loadEstateOperation.start(accId, zoneId)

View file

@ -29,7 +29,6 @@ class TrunkGUI(StateData.StateData):
self.swapShoesEvent = swapShoesEvent self.swapShoesEvent = swapShoesEvent
self.deleteEvent = deleteEvent self.deleteEvent = deleteEvent
self.cancelEvent = cancelEvent self.cancelEvent = cancelEvent
self.genderChange = 0
self.verify = None self.verify = None
return return
@ -324,9 +323,7 @@ class TrunkGUI(StateData.StateData):
task = Task(self.rotateToonR) task = Task(self.rotateToonR)
taskMgr.add(task, self.taskName('rotateR')) taskMgr.add(task, self.taskName('rotateR'))
def setGender(self, gender):
self.ownerGender = gender
self.genderChange = 1
def swapHat(self, offset): def swapHat(self, offset):
length = len(self.hats) length = len(self.hats)

View file

@ -0,0 +1,564 @@
#################################################################
# class: BingoManagerAI.py
#
# Purpose: Manages the Bingo Night Holiday for all ponds in all
# hoods. It generates PondBingoManagerAI objects for
# every pond and shuts them down respectively. In
# addition, it should handle all Stat collection such
# as top jackpot of the night, top bingo players, and
# so forth.
#
# Note: Eventually, this will derive from the HolidayBase class
# and run each and ever Bingo Night, whenever that has
# been decided upon.
#################################################################
#################################################################
# Direct Specific Modules
#################################################################
from direct.distributed import DistributedObjectAI
from direct.distributed.ClockDelta import *
from direct.directnotify import DirectNotifyGlobal
from otp.otpbase import PythonUtil
from direct.task import Task
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.estate import DistributedEstateAI
from toontown.fishing import BingoGlobals
from toontown.fishing import DistributedFishingPondAI
from toontown.fishing import DistributedPondBingoManagerAI
from direct.showbase import RandomNumGen
from toontown.toonbase import ToontownGlobals
from toontown.hood import ZoneUtil
#################################################################
# Python Specific Modules
#################################################################
import pickle
import os
import time
#################################################################
# Globals and Constants
#################################################################
TTG = ToontownGlobals
BG = BingoGlobals
class BingoManagerAI(object):
# __metaclass__ = PythonUtil.Singleton
notify = DirectNotifyGlobal.directNotify.newCategory("BingoManagerAI")
#notify.setDebug(True)
#notify.setInfo(True)
serverDataFolder = simbase.config.GetString('server-data-folder', "dependencies/backups/bingo")
DefaultReward = { TTG.DonaldsDock: [BG.MIN_SUPER_JACKPOT, 1],
TTG.ToontownCentral: [BG.MIN_SUPER_JACKPOT, 1],
TTG.TheBrrrgh: [BG.MIN_SUPER_JACKPOT, 1],
TTG.MinniesMelodyland: [BG.MIN_SUPER_JACKPOT, 1],
TTG.DaisyGardens: [BG.MIN_SUPER_JACKPOT, 1],
TTG.DonaldsDreamland: [BG.MIN_SUPER_JACKPOT, 1],
TTG.MyEstate: [BG.MIN_SUPER_JACKPOT, 1] }
############################################################
# Method: __init__
# Purpose: This method initializes the BingoManagerAI object
# and generates the PondBingoManagerAI.
# Input: air - The AI Repository.
# Output: None
############################################################
def __init__(self, air):
self.air = air
# Dictionaries for quick reference to the DPMAI
self.doId2do = {}
self.zoneId2do = {}
self.hood2doIdList = { TTG.DonaldsDock: [],
TTG.ToontownCentral: [],
TTG.TheBrrrgh: [],
TTG.MinniesMelodyland: [],
TTG.DaisyGardens: [],
TTG.DonaldsDreamland: [],
TTG.MyEstate: [] }
self.__hoodJackpots = {}
self.finalGame = BG.NORMAL_GAME
self.shard = str(air.districtId)
self.waitTaskName = 'waitForIntermission'
# Generate the Pond Bingo Managers
self.generateBingoManagers()
############################################################
# Method: start
# Purpose: This method "starts" each PondBingoManager for
# the Bingo Night Holidy.
# Input: None
# Output: None
############################################################
def start(self):
# Iterate through keys and change into "active" state
# for the pondBingoManagerAI
self.notify.info("Starting Bingo Night Event: %s" % (time.ctime()))
self.air.bingoMgr = self
# Determine current time so that we can gracefully handle
# an AI crash or reboot during Bingo Night.
currentMin = time.localtime()[4]
self.timeStamp = globalClockDelta.getRealNetworkTime()
initState = ((currentMin < BG.HOUR_BREAK_MIN) and ['Intro'] or ['Intermission'])[0]
# CHEATS
#initState = 'Intermission'
for do in list(self.doId2do.values()):
do.startup(initState)
self.waitForIntermission()
# tell everyone bingo night is starting
simbase.air.newsManager.bingoStart()
############################################################
# Method: stop
# Purpose: This method begins the process of shutting down
# bingo night. It is called whenever the
# BingoNightHolidayAI is told to close for the
# evening.
# Input: None
# Output: None
############################################################
def stop(self):
self.__startCloseEvent()
############################################################
# Method: __shutdown
# Purpose: This method performs the actual shutdown sequence
# for the pond bingo manager. By this point, all
# of the PondBingoManagerAIs should have shutdown
# so we can safely close.
# Input: None
# Output: None
############################################################
def shutdown(self):
self.notify.info('__shutdown: Shutting down BingoManager')
# tell everyone bingo night is stopping
simbase.air.newsManager.bingoEnd()
if self.doId2do:
#self.notify.warning('__shutdown: Not all PondBingoManagers have shutdown! Manual Shutdown for Memory sake.')
for bingoMgr in list(self.doId2do.values()):
self.notify.info("__shutdown: shutting down PondBinfoManagerAI in zone %s" % bingoMgr.zoneId)
bingoMgr.shutdown()
self.doId2do.clear()
del self.doId2do
self.air.bingoMgr = None
del self.air
del self.__hoodJackpots
############################################################
# Method: __resumeBingoNight
# Purpose: This method resumes Bingo Night after an
# an intermission has taken place. This should
# start on the hour.
# Input: None
# Output: None
############################################################
def __resumeBingoNight(self, task):
self.__hoodJackpots = self.load()
for bingoMgr in list(self.doId2do.values()):
if bingoMgr.isGenerated():
if self.finalGame:
bingoMgr.setFinalGame(self.finalGame)
bingoMgr.resumeBingoNight()
timeToWait = BG.getGameTime(BG.BLOCKOUT_CARD) + BG.TIMEOUT_SESSION + 5.0
taskMgr.doMethodLater(timeToWait, self.__handleSuperBingoClose, 'SuperBingoClose')
# If we have another game after this, then do not want to generate a
# new task to wait for the next intermission.
return Task.done
############################################################
# Method: __handleSuperBingoClose
# Purpose: This method is responsible for logging the
# current hood jackpot amounts to the .jackpot
# "database" file. In addition, it initiates the
# shutdown of the BingoManagerAI if the final
# game of the evening has been played.
# Input: task - a task that is spawned by a doMethodLater
# Output: None
############################################################
def __handleSuperBingoClose(self, task):
# Save Jackpot Data to File
self.notify.info("handleSuperBingoClose: Saving Hood Jackpots to DB")
self.notify.info("handleSuperBingoClose: hoodJackpots %s" %(self.__hoodJackpots))
for hood in list(self.__hoodJackpots.keys()):
if self.__hoodJackpots[hood][1]:
self.__hoodJackpots[hood][0] += BG.ROLLOVER_AMOUNT
# clamp it if it exceeds jackpot total
if self.__hoodJackpots[hood][0] > BG.MAX_SUPER_JACKPOT:
self.__hoodJackpots[hood][0] = BG.MAX_SUPER_JACKPOT
else:
self.__hoodJackpots[hood][1] = BG.MIN_SUPER_JACKPOT
taskMgr.remove(task)
self.save()
if self.finalGame:
self.shutdown()
return
self.waitForIntermission()
############################################################
# Method: __handleIntermission
# Purpose: This wrapper method tells the intermission to
# start.
# Input: task - a task that is spawned by a doMethodLater
# Output: None
############################################################
def __handleIntermission(self, task):
self.__startIntermission()
############################################################
# Method: getIntermissionTime
# Purpose: This method returns the time of when an
# intermission began. It is meant to provide a
# fairly accurate time countdown for the clients.
# Input: None
# Output: returns the timestamp of intermission start
############################################################
def getIntermissionTime(self):
return self.timeStamp
############################################################
# Method: __startIntermission
# Purpose: This method is responsible for starting the
# hourly intermission for bingo night.
# Input: None
# Output: None
############################################################
def __startIntermission(self):
for bingoMgr in list(self.doId2do.values()):
bingoMgr.setFinalGame(BG.INTERMISSION)
if not self.finalGame:
currentTime = time.localtime()
currentMin = currentTime[4]
currentSec = currentTime[5]
# Calculate time until the next hour
waitTime = (60-currentMin)*60 - currentSec
sec = (currentMin - BG.HOUR_BREAK_MIN)*60 + currentSec
self.timeStamp = globalClockDelta.getRealNetworkTime() - sec
self.notify.info('__startIntermission: Timestamp %s'%(self.timeStamp))
else:
# In case someone should decide that bingo night does not end on the hour, ie 30 past,
# then this will allow a five minute intermission to sync up the PBMgrAIs for the
# final game.
waitTime = BG.HOUR_BREAK_SESSION
self.timeStamp = globalClockDelta.getRealNetworkTime()
self.waitTaskName = 'waitForEndOfIntermission'
self.notify.info('__startIntermission: Waiting %s seconds until Bingo Night resumes.' %(waitTime))
taskMgr.doMethodLater(waitTime, self.__resumeBingoNight, self.waitTaskName)
return Task.done
############################################################
# Method: __waitForIntermission
# Purpose: This method is responsible for calculating the
# wait time for the hourly intermission for bingo
# night.
# Input: None
# Output: None
############################################################
def waitForIntermission(self):
currentTime = time.localtime()
currentMin = currentTime[4]
currentSec = currentTime[5]
# Calculate Amount of time needed for one normal game of Bingo from the
# Waitcountdown all the way to the gameover. (in secs)
if currentMin >= BG.HOUR_BREAK_MIN:
# If the AI starts during bingo night and after the intermission start(a crash or scheduled downtime),
# then immediately start the intermission to sync all the clients up for the next hour.
self.__startIntermission()
else:
waitTime = ((BG.HOUR_BREAK_MIN - currentMin)*60) - currentSec
self.waitTaskName = 'waitForIntermission'
self.notify.info("Waiting %s seconds until Final Game of the Hour should be announced." % (waitTime))
taskMgr.doMethodLater(waitTime, self.__handleIntermission, self.waitTaskName)
############################################################
# Method: generateBingoManagers
# Purpose: This method creates a PondBingoManager for each
# pond that is found within the hoods. It searches
# through each hood for pond objects and generates
# the corresponding ManagerAI objects.
# Input: None
# Output: None
############################################################
def generateBingoManagers(self):
# Create DPBMAI for all ponds in all hoods.
for hood in self.air.hoods:
self.createPondBingoMgrAI(hood)
# Create DPBMAI for every pond in every active estate.
for estateAI in list(self.air.estateMgr.estate.values()):
self.createPondBingoMgrAI(estateAI)
############################################################
# Method: addDistObj
# Purpose: This method adds the newly created Distributed
# object to the BingoManagerAI doId2do list for
# easy reference.
# Input: distObj
# Output: None
############################################################
def addDistObj(self, distObj):
self.notify.debug("addDistObj: Adding %s : %s" % (distObj.getDoId(), distObj.zoneId))
self.doId2do[distObj.getDoId()] = distObj
self.zoneId2do[distObj.zoneId] = distObj
def __hoodToUse(self, zoneId):
hood = ZoneUtil.getCanonicalHoodId(zoneId)
if hood >= TTG.DynamicZonesBegin:
hood = TTG.MyEstate
return hood
############################################################
# Method: createPondBingoMgrAI
# Purpose: This method generates PBMgrAI instances for
# each pond found in the specified hood. A hood
# may be an estate or an actual hood.
# Input: hood - HoodDataAI or EstateAI object.
# dynamic - Will be 1 only if an Estate was generated
# after Bingo Night has started.
# Output: None
############################################################
def createPondBingoMgrAI(self, hood, dynamic=0):
if hood.fishingPonds == None:
self.notify.warning("createPondBingoMgrAI: hood doesn't have any ponds... were they deleted? %s" % hood)
return
for pond in hood.fishingPonds:
# First, optain hood id based on zone id that the pond is located in.
hoodId = self.__hoodToUse(pond.zoneId)
if hoodId not in self.hood2doIdList:
# for now don't start it for minigolf zone and outdoor zone
continue
bingoMgr = DistributedPondBingoManagerAI.DistributedPondBingoManagerAI(self.air, pond)
bingoMgr.generateWithRequired(pond.zoneId)
self.addDistObj(bingoMgr)
if hasattr(hood, "addDistObj"):
hood.addDistObj(bingoMgr)
pond.setPondBingoManager(bingoMgr)
# Add the PBMgrAI reference to the hood2doIdList.
self.hood2doIdList[hoodId].append(bingoMgr.getDoId())
# Dynamic if this method was called when an estate was generated after
# Bingo Night has started.
if dynamic:
self.startDynPondBingoMgrAI(bingoMgr)
############################################################
# Method: startDynPondBingoMgrAI
# Purpose: This method determines what state a Dynamic
# Estate PBMgrAI should start in, and then it tells
# the PBMgrAI to start.
# Input: bingoMgr - PondBongoMgrAI Instance
# Output: None
############################################################
def startDynPondBingoMgrAI(self, bingoMgr):
currentMin = time.localtime()[4]
# If the dynamic estate is generated before the intermission starts
# and it is not the final game of the night, then the PBMgrAI should start
# in the WaitCountdown state. Otherwise, it should start in the intermission
# state so that it can sync up with all of the other Estate PBMgrAIs for the
# super bingo game.
initState = (((currentMin < BG.HOUR_BREAK_MIN) and (not self.finalGame)) and ['WaitCountdown'] or ['Intermission'])[0]
bingoMgr.startup(initState)
############################################################
# Method: removePondBingoMgrAI
# Purpose: This method generates PBMgrAI instances for
# each pond found in the specified hood. A hood
# may be an estate or an actual hood.
# Input: doId - the doId of the PBMgrAI that should be
# removed from the dictionaries.
# Output: None
############################################################
def removePondBingoMgrAI(self, doId):
if doId in self.doId2do:
zoneId = self.doId2do[doId].zoneId
self.notify.info('removePondBingoMgrAI: Removing PondBingoMgrAI %s' %(zoneId))
hood = self.__hoodToUse(zoneId)
self.hood2doIdList[hood].remove(doId)
del self.zoneId2do[zoneId]
del self.doId2do[doId]
else:
self.notify.debug('removeBingoManager: Attempt to remove invalid PondBingoManager %s' % (doId))
############################################################
# Method: SetFishForPlayer
# Purpose: This method adds the newly created Distributed
# object to the BingoManagerAI doId2do list for
# easy reference.
# Input: distObj
# Output: None
############################################################
def setAvCatchForPondMgr(self, avId, zoneId, catch):
self.notify.info('setAvCatchForPondMgr: zoneId %s' %(zoneId))
if zoneId in self.zoneId2do:
self.zoneId2do[zoneId].setAvCatch(avId, catch)
else:
self.notify.info('setAvCatchForPondMgr Failed: zoneId %s' %(zoneId))
############################################################
# Method: getFileName
# Purpose: This method constructs the jackpot filename for
# a particular shard.
# Input: None
# Output: returns jackpot filename
############################################################
def getFileName(self):
"""Figure out the path to the saved state"""
f = "%s%s.jackpot" % (self.serverDataFolder, self.shard)
return f
############################################################
# Method: saveTo
# Purpose: This method saves the current jackpot ammounts
# to the specified file.
# Input: file - file to save jackpot amounts
# Output: None
############################################################
def saveTo(self, file):
pickle.dump(self.__hoodJackpots, file)
############################################################
# Method: save
# Purpose: This method determines where to save the jackpot
# amounts.
# Input: None
# Output: None
############################################################
def save(self):
"""Save data to default location"""
try:
fileName = self.getFileName()
backup = fileName+ '.jbu'
if os.path.exists(fileName):
os.rename(fileName, backup)
file = open(fileName, 'wb')
file.seek(0)
self.saveTo(file)
file.close()
if os.path.exists(backup):
os.remove(backup)
except EnvironmentError:
self.notify.warning(str(sys.exc_info()[1]))
############################################################
# Method: loadFrom
# Purpose: This method loads the jackpot amounts from the
# specified file.
# Input: File - file to load amount from
# Output: returns a dictionary of the jackpots for this shard
############################################################
def loadFrom(self, file):
# Default Jackpot Amount
jackpots = self.DefaultReward
try:
jackpots = pickle.load(file)
except EOFError:
pass
return jackpots
############################################################
# Method: load
# Purpose: This method determines where to load the jackpot
# amounts.
# Input: None
# Output: None
############################################################
def load(self):
"""Load Jackpot data from default location"""
fileName = self.getFileName()
try:
file = open(fileName+'.jbu', 'rb')
if os.path.exists(fileName):
os.remove(fileName)
except IOError:
try:
file = open(fileName)
except IOError:
# Default Jackpot Amount
return self.DefaultReward
file.seek(0)
jackpots = self.loadFrom(file)
file.close()
return jackpots
############################################################
# Method: getSuperJackpot
# Purpose: This method returns the super jackpot amount for
# the specified zone. It calculates which hood
# the zone is in and returns the shared jackpot
# amount for that hood.
# Input: zoneId - retrieve jackpot for this zone's hood
# Output: returns jackpot for hood that zoneid is found in
############################################################
def getSuperJackpot(self, zoneId):
hood = self.__hoodToUse(zoneId)
self.notify.info('getSuperJackpot: hoodJackpots %s \t hood %s' % (self.__hoodJackpots, hood))
return self.__hoodJackpots.get(hood, [BG.MIN_SUPER_JACKPOT])[0]
############################################################
# Method: __startCloseEvent
# Purpose: This method starts to close Bingo Night down. One
# more super card game will be played at the end
# of the hour(unless the times are changed).
# Input: None
# Output: None
############################################################
def __startCloseEvent(self):
self.finalGame = BG.CLOSE_EVENT
if self.waitTaskName == 'waitForIntermission':
taskMgr.remove(self.waitTaskName)
self.__startIntermission()
############################################################
# Method: handleSuperBingoWin
# Purpose: This method handles a victory when a super
# bingo game has been one. It updates the jackpot
# amount and tells each of the other ponds in that
# hood that they did not win.
# Input: zoneId - pond who won the bingo game.
# Output: None
############################################################
def handleSuperBingoWin(self, zoneId):
# Reset the Jackpot and unmark the dirty bit.
hood = self.__hoodToUse(zoneId)
self.__hoodJackpots[hood][0] = self.DefaultReward[hood][0]
self.__hoodJackpots[hood][1] = 0
# tell everyone who won
#simbase.air.newsManager.bingoWin(zoneId)
# Tell the other ponds that they did not win and should handle the loss
for doId in self.hood2doIdList[hood]:
distObj = self.doId2do[doId]
if distObj.zoneId != zoneId:
self.notify.info("handleSuperBingoWin: Did not win in zone %s" %(distObj.zoneId))
distObj.handleSuperBingoLoss()

View file

@ -0,0 +1,89 @@
#################################################################
# class: BingoNightHolidayAI.py
#
# Purpose: Manages the Bingo Night Holiday for all ponds in all
# hoods.
# Note: The Holiday Manager System(HMS) deletes each Holiday AI
# instance when the particular Holiday Expires. Unfortunately,this
# sort of functionality is not ideal for the BingoManagerAI
# class because we want to allow players to finish a final
# game of bingo before the BingoManagerAI shuts down.
#
# In order to prevent the BingoManagerAI from shutting down
# unexpectantly in the middle of a game, we provide this
# class to act as a "buffer" between the HSM
# and the BingoManagerAI. This class is created
# and destroyed by the HMS. When the HMS tells this class
# to start, it instantiates a BingoManagerAI object to
# run the actual Bingo Night Holiday.
#
# When the stop call is received, it tells the BingoManagerAI
# to stop after the next game and then it removes the reference
# to the BingoManagerAI. A reference to the BingoManagerAI still
# remains in the AIR so that the BingoManagerAI can finish
# the final Bingo Night Games before it deletes itself.
#################################################################
#################################################################
# Direct Specific Modules
#################################################################
from direct.directnotify import DirectNotifyGlobal
from otp.otpbase import PythonUtil
from direct.task import Task
#################################################################
# Toontown Specific Modules
#################################################################
from toontown.ai import HolidayBaseAI
from toontown.fishing import BingoGlobals
from toontown.fishing import BingoManagerAI
#################################################################
# Python Specific Modules
#################################################################
import time
class BingoNightHolidayAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory('BingoNightHolidayAI')
############################################################
# Method: __init__
# Purpose: This method initializes the HolidayBaseAI
# base class.
# Input: air - The AI Repository.
# Output: None
############################################################
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
############################################################
# Method: start
# Purpose: This method instantiates a BingoManagerAI and
# tells it to start up bingo night.
# Input: None
# Output: None
############################################################
def start(self):
if self.air.bingoMgr:
raise PythonUtil.SingletonError("Bingo Manager already Exists! DO NOT RUN HOLIDAY!!")
else:
self.notify.info('Starting BingoNight Holiday: %s' % (time.ctime()))
self.bingoMgr = BingoManagerAI.BingoManagerAI(self.air)
self.bingoMgr.start()
############################################################
# Method: start
# Purpose: This method tells the BingoManagerAI to shutdown
# and removes the reference. The BingoManagerAI
# does not actually shutdown until it finish all
# the PBMgrAIs have done so. The AIR maintains a
# reference to the BingoManagerAI so this method
# does not actually delete it.
# Input: None
# Output: None
############################################################
def stop(self):
if self.bingoMgr:
self.notify.info('stop: Tell the BingoManagerAI to stop BingoNight Holiday - %s' %(time.ctime()))
self.bingoMgr.stop()
del self.bingoMgr

View file

@ -0,0 +1,35 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
class ToontownFriendsManager(DistributedObjectGlobal):
notify = DirectNotifyGlobal.directNotify.newCategory('ToontownFriendsManager')
def sendGetFriendsListRequest(self):
self.sendUpdate('getFriendsListRequest')
def getFriendsListResponse(self, success, friendsList):
if not success:
self.notify.warning('An error has occurred when retrieving friends list.')
self.cr.friendsListError = 1
self.cr.setFriendsMap(friendsList)
def friendOnline(self, friendId, commonChatFlags, whitelistChatFlags):
self.cr.setFriendOnline(friendId, commonChatFlags, whitelistChatFlags)
def friendOffline(self, friendId):
self.cr.setFriendOffline(friendId)
def sendGetAvatarDetailsRequest(self, avId):
self.sendUpdate('getAvatarDetailsRequest', [avId])
def getAvatarDetailsResponse(self, avatarDetails):
datagram = PyDatagram(avatarDetails)
di = PyDatagramIterator(datagram)
self.cr.handleGetAvatarDetailsResp(di)
def sendRemoveFriend(self, friendId):
self.sendUpdate('removeFriend', [friendId])

View file

@ -0,0 +1,26 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectGlobalAI import DistributedObjectGlobalAI
from direct.distributed.PyDatagram import *
class ToontownFriendsManagerAI(DistributedObjectGlobalAI):
notify = DirectNotifyGlobal.directNotify.newCategory('ToontownFriendsManagerAI')
def sendFriendOnline(self, avId, friendId, commonChatFlags, whitelistChatFlags):
self.sendUpdateToAvatarId(avId, 'friendOnline', [friendId, commonChatFlags, whitelistChatFlags])
def sendMakeFriends(self, avatarAId, avatarBId, flags, context):
self.sendUpdate('makeFriends', [avatarAId, avatarBId, flags, context])
def makeFriendsResponse(self, avatarAId, avatarBId, result, context):
if result == 1:
avatarA = self.air.doId2do.get(avatarAId)
avatarB = self.air.doId2do.get(avatarBId)
if avatarA and avatarB:
self.sendFriendOnline(avatarAId, avatarBId, 0, 1)
self.sendFriendOnline(avatarBId, avatarAId, 0, 1)
messenger.send("makeFriendsReply", [result, context])
def sendRequestSecret(self, requesterId):
self.sendUpdate('requestSecret', [requesterId])

View file

@ -0,0 +1,488 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
from direct.distributed.PyDatagram import *
from otp.otpbase import OTPGlobals
class FriendsOperation:
def __init__(self, friendsManager, sender):
self.friendsManager = friendsManager
self.sender = sender
def _handleDone(self):
# TODO
pass
def _handleError(self, error):
# TODO
pass
class GetFriendsListOperation(FriendsOperation):
def __init__(self, friendsManager, sender):
FriendsOperation.__init__(self, friendsManager, sender)
self.friendsList = None
self.tempFriendsList = None
self.onlineFriends = None
self.currentFriendIdx = None
def start(self):
self.friendsList = []
self.tempFriendsList = []
self.onlineFriends = []
self.currentFriendIdx = 0
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.sender,
self.__handleSenderRetrieved)
def __handleSenderRetrieved(self, dclass, fields):
if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']:
self._handleError('Retrieved sender is not a DistributedToonUD!')
return
self.tempFriendsList = fields['setFriendsList'][0]
if len(self.tempFriendsList) <= 0:
self._handleDone()
return
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.tempFriendsList[0][0],
self.__handleFriendRetrieved)
def __handleFriendRetrieved(self, dclass, fields):
if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']:
self._handleError('Retrieved friend is not a DistributedToonUD!')
return
friendId = self.tempFriendsList[self.currentFriendIdx][0]
self.friendsList.append([friendId, fields['setName'][0], fields['setDNAString'][0], fields['setPetId'][0]])
if len(self.friendsList) >= len(self.tempFriendsList):
self.__checkFriendsOnline()
return
self.currentFriendIdx += 1
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId,
self.tempFriendsList[self.currentFriendIdx][0],
self.__handleFriendRetrieved)
def __checkFriendsOnline(self):
self.currentFriendIdx = 0
for friendDetails in self.friendsList:
self.friendsManager.air.getActivated(friendDetails[0], self.__gotActivatedResp)
def __gotActivatedResp(self, avId, activated):
self.currentFriendIdx += 1
if activated:
self.onlineFriends.append(avId)
if self.currentFriendIdx >= len(self.friendsList):
self._handleDone()
def __sendFriendsList(self, success):
self.friendsManager.sendUpdateToAvatarId(self.sender, 'getFriendsListResponse', [success, self.friendsList if success else []])
for friendId in self.onlineFriends:
self.friendsManager.sendFriendOnline(self.sender, friendId, 0, 1)
def _handleDone(self):
self.__sendFriendsList(True)
FriendsOperation._handleDone(self)
def _handleError(self, error):
self.__sendFriendsList(False)
FriendsOperation._handleError(self, error)
class GetAvatarDetailsOperation(FriendsOperation):
def __init__(self, friendsManager, sender):
FriendsOperation.__init__(self, friendsManager, sender)
self.avId = None
self.dclass = None
self.fields = None
def start(self, avId):
self.avId = avId
self.fields = {}
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, avId,
self.__handleAvatarRetrieved)
def __handleAvatarRetrieved(self, dclass, fields):
if dclass not in (self.friendsManager.air.dclassesByName['DistributedToonUD'],
self.friendsManager.air.dclassesByName['DistributedPetAI']):
self._handleError('Retrieved avatar is not a DistributedToonUD or DistributedPetAI!')
return
self.dclass = dclass
self.fields = fields
self.fields['avId'] = self.avId
self._handleDone()
def __packAvatarDetails(self, dclass, fields):
# Pack required fields.
fieldPacker = DCPacker()
for i in range(dclass.getNumInheritedFields()):
field = dclass.getInheritedField(i)
if not field.isRequired() or field.asMolecularField():
continue
k = field.getName()
v = fields.get(k, None)
fieldPacker.beginPack(field)
if not v:
fieldPacker.packDefaultValue()
else:
field.packArgs(fieldPacker, v)
fieldPacker.endPack()
return fieldPacker.getBytes()
def __sendAvatarDetails(self, success):
datagram = PyDatagram()
datagram.addUint32(self.fields['avId']) # avId
datagram.addUint8(0 if success else 1) # returnCode
if success:
avatarDetails = self.__packAvatarDetails(self.dclass, self.fields)
datagram.appendData(avatarDetails) # fields
self.friendsManager.sendUpdateToAvatarId(self.sender, 'getAvatarDetailsResponse', [datagram.getMessage()])
def _handleDone(self):
self.__sendAvatarDetails(True)
FriendsOperation._handleDone(self)
def _handleError(self, error):
self.__sendAvatarDetails(False)
FriendsOperation._handleError(self, error)
class MakeFriendsOperation(FriendsOperation):
def __init__(self, friendsManager):
FriendsOperation.__init__(self, friendsManager, None)
self.avatarAId = None
self.avatarBId = None
self.flags = None
self.context = None
self.resultCode = None
self.onlineToons = None
self.avatarAFriendsList = None
self.avatarBFriendsList = None
def start(self, avatarAId, avatarBId, flags, context):
self.avatarAId = avatarAId
self.avatarBId = avatarBId
self.flags = flags
self.context = context
self.resultCode = 0
self.onlineToons = []
self.friendsManager.air.getActivated(self.avatarAId, self.__gotActivatedAvatarA)
def __handleActivatedResp(self, avId, activated):
if activated:
self.onlineToons.append(avId)
def __gotActivatedAvatarA(self, avId, activated):
self.__handleActivatedResp(avId, activated)
self.friendsManager.air.getActivated(self.avatarBId, self.__gotActivatedAvatarB)
def __handleMakeFriends(self, dclass, fields, friendId):
if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']:
self._handleError('Retrieved avatar is not a DistributedToonUD!')
return False, []
friendsList = fields['setFriendsList'][0]
if len(friendsList) >= OTPGlobals.MaxFriends:
self._handleError('Avatar\'s friends list is full!')
return False, []
newFriend = (friendId, self.flags)
if newFriend in friendsList:
self._handleError('Already friends!')
return False, []
friendsList.append(newFriend)
return True, friendsList
def __handleAvatarARetrieved(self, dclass, fields):
success, avatarAFriendsList = self.__handleMakeFriends(dclass, fields, self.avatarBId)
if success:
self.avatarAFriendsList = avatarAFriendsList
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.avatarBId,
self.__handleAvatarBRetrieved)
def __gotActivatedAvatarB(self, avId, activated):
self.__handleActivatedResp(avId, activated)
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.avatarAId,
self.__handleAvatarARetrieved)
def __handleAvatarBRetrieved(self, dclass, fields):
success, avatarBFriendsList = self.__handleMakeFriends(dclass, fields, self.avatarAId)
if success:
self.avatarBFriendsList = avatarBFriendsList
self._handleDone()
def __handleSetFriendsList(self, avId, friendsList):
if avId in self.onlineToons:
self.friendsManager.sendUpdateToAvatar(avId, 'setFriendsList', [friendsList])
else:
self.friendsManager.air.dbInterface.updateObject(self.friendsManager.air.dbId, avId,
self.friendsManager.air.dclassesByName[
'DistributedToonUD'],
{'setFriendsList': [friendsList]})
def _handleDone(self):
self.resultCode = 1
if self.avatarAFriendsList is not None and self.avatarBFriendsList is not None:
self.__handleSetFriendsList(self.avatarAId, self.avatarAFriendsList)
self.__handleSetFriendsList(self.avatarBId, self.avatarBFriendsList)
if self.avatarAId in self.onlineToons and self.avatarBId in self.onlineToons:
self.friendsManager.declareObject(self.avatarAId, self.avatarBId)
self.friendsManager.declareObject(self.avatarBId, self.avatarAId)
self.friendsManager.sendMakeFriendsResponse(self.avatarAId, self.avatarBId, self.resultCode, self.context)
FriendsOperation._handleDone(self)
def _handleError(self, error):
self.resultCode = 0
self.friendsManager.sendMakeFriendsResponse(self.avatarAId, self.avatarBId, self.resultCode, self.context)
FriendsOperation._handleError(self, error)
class RemoveFriendOperation(FriendsOperation):
def __init__(self, friendsManager, sender):
FriendsOperation.__init__(self, friendsManager, sender)
self.friendId = None
self.onlineToons = None
self.senderFriendsList = None
self.friendFriendsList = None
def start(self, friendId):
self.friendId = friendId
self.onlineToons = []
self.friendsManager.air.getActivated(self.sender, self.__gotSenderActivated)
def __handleActivatedResp(self, avId, activated):
if activated:
self.onlineToons.append(avId)
def __gotSenderActivated(self, avId, activated):
self.__handleActivatedResp(avId, activated)
self.friendsManager.air.getActivated(self.friendId, self.__gotFriendActivated)
def __gotFriendActivated(self, avId, activated):
self.__handleActivatedResp(avId, activated)
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.sender,
self.__handleSenderRetrieved)
def __handleRemoveFriend(self, dclass, fields, friendId):
if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']:
self._handleError('Retrieved sender is not a DistributedToonUD!')
return False, []
friendsList = fields['setFriendsList'][0]
removed = False
for index, friend in enumerate(friendsList):
if friend[0] == friendId:
del friendsList[index]
removed = True
break
if removed:
return True, friendsList
else:
self._handleError('Unable to remove friend!')
return False, []
def __handleSenderRetrieved(self, dclass, fields):
success, senderFriendsList = self.__handleRemoveFriend(dclass, fields, self.friendId)
if success:
self.senderFriendsList = senderFriendsList
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.friendId,
self.__handleFriendRetrieved)
def __handleFriendRetrieved(self, dclass, fields):
success, friendFriendsList = self.__handleRemoveFriend(dclass, fields, self.sender)
if success:
self.friendFriendsList = friendFriendsList
self._handleDone()
def __handleSetFriendsList(self, avId, friendsList):
if avId in self.onlineToons:
self.friendsManager.sendUpdateToAvatar(avId, 'setFriendsList', [friendsList])
else:
self.friendsManager.air.dbInterface.updateObject(self.friendsManager.air.dbId, avId,
self.friendsManager.air.dclassesByName[
'DistributedToonUD'],
{'setFriendsList': [friendsList]})
def _handleDone(self):
if self.senderFriendsList is not None and self.friendFriendsList is not None:
self.__handleSetFriendsList(self.sender, self.senderFriendsList)
self.__handleSetFriendsList(self.friendId, self.friendFriendsList)
if self.sender in self.onlineToons and self.friendId in self.onlineToons:
self.friendsManager.undeclareObject(self.sender, self.friendId)
self.friendsManager.undeclareObject(self.friendId, self.sender)
if self.friendId in self.onlineToons:
self.friendsManager.sendUpdateToAvatar(self.friendId, 'friendsNotify', [self.sender, 1])
FriendsOperation._handleDone(self)
class ComingOnlineOperation(FriendsOperation):
def __init__(self, friendsManager):
FriendsOperation.__init__(self, friendsManager, None)
self.avId = None
self.friendsList = None
self.currentFriendIdx = None
def start(self, avId, friendsList):
self.avId = avId
self.friendsList = friendsList
self.__checkFriendsOnline()
def __checkFriendsOnline(self):
self.currentFriendIdx = 0
for friendId in self.friendsList:
self.friendsManager.air.getActivated(friendId, self.__gotFriendActivated)
def __gotFriendActivated(self, avId, activated):
self.currentFriendIdx += 1
if activated:
self.friendsManager.declareObject(avId, self.avId)
self.friendsManager.declareObject(self.avId, avId)
self.friendsManager.sendFriendOnline(avId, self.avId, 0, 1)
if self.currentFriendIdx >= len(self.friendsList):
self._handleDone()
class GoingOfflineOperation(FriendsOperation):
def __init__(self, friendsManager):
FriendsOperation.__init__(self, friendsManager, None)
self.avId = None
self.friendsList = None
self.accId = None
self.currentFriendIdx = None
def start(self, avId):
self.avId = avId
self.friendsList = []
self.accId = 0
self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.avId, self.__handleAvatarRetrieved)
def __handleAvatarRetrieved(self, dclass, fields):
if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']:
self._handleError('Retrieved avatar is not a DistributedToonUD!')
return
self.friendsList = fields['setFriendsList'][0]
self.accId = fields['setDISLid'][0]
self.__checkFriendsOnline()
def __checkFriendsOnline(self):
self.currentFriendIdx = 0
for friendId, _ in self.friendsList:
self.friendsManager.air.getActivated(friendId, self.__gotFriendActivated)
def __gotFriendActivated(self, avId, activated):
self.currentFriendIdx += 1
if activated:
self.friendsManager.undeclareObject(avId, self.avId)
self.friendsManager.undeclareObject(self.accId, avId, isAccount=True)
self.friendsManager.sendFriendOffline(avId, self.avId)
if self.currentFriendIdx >= len(self.friendsList):
self._handleDone()
class ToontownFriendsManagerUD(DistributedObjectGlobalUD):
notify = DirectNotifyGlobal.directNotify.newCategory('ToontownFriendsManagerUD')
def __init__(self, air):
DistributedObjectGlobalUD.__init__(self, air)
self.operations = []
self.secrets = []
def sendMakeFriendsResponse(self, avatarAId, avatarBId, result, context):
self.sendUpdate('makeFriendsResponse', [avatarAId, avatarBId, result, context])
def declareObject(self, doId, objId):
datagram = PyDatagram()
datagram.addServerHeader(self.GetPuppetConnectionChannel(doId), self.air.ourChannel, CLIENTAGENT_DECLARE_OBJECT)
datagram.addUint32(objId)
datagram.addUint16(self.air.dclassesByName['DistributedToonUD'].getNumber())
self.air.send(datagram)
def undeclareObject(self, doId, objId, isAccount=False):
datagram = PyDatagram()
if isAccount:
datagram.addServerHeader(self.GetAccountConnectionChannel(doId), self.air.ourChannel,
CLIENTAGENT_UNDECLARE_OBJECT)
else:
datagram.addServerHeader(self.GetPuppetConnectionChannel(doId), self.air.ourChannel,
CLIENTAGENT_UNDECLARE_OBJECT)
datagram.addUint32(objId)
self.air.send(datagram)
def sendFriendOnline(self, avId, friendId, commonChatFlags, whitelistChatFlags):
self.sendUpdateToAvatarId(avId, 'friendOnline', [friendId, commonChatFlags, whitelistChatFlags])
def sendFriendOffline(self, avId, friendId):
self.sendUpdateToAvatarId(avId, 'friendOffline', [friendId])
def sendUpdateToAvatar(self, avId, fieldName, args=[]):
dclass = self.air.dclassesByName['DistributedToonUD']
if not dclass:
return
field = dclass.getFieldByName(fieldName)
if not field:
return
datagram = field.aiFormatUpdate(avId, avId, self.air.ourChannel, args)
self.air.send(datagram)
def runSenderOperation(self, operationType, *args):
sender = self.air.getAvatarIdFromSender()
if not sender:
return
newOperation = operationType(self, sender)
self.operations.append(newOperation)
newOperation.start(*args)
def runServerOperation(self, operationType, *args):
newOperation = operationType(self)
self.operations.append(newOperation)
newOperation.start(*args)
def getFriendsListRequest(self):
self.runSenderOperation(GetFriendsListOperation)
def getAvatarDetailsRequest(self, avId):
self.runSenderOperation(GetAvatarDetailsOperation, avId)
def makeFriends(self, avatarAId, avatarBId, flags, context):
self.runServerOperation(MakeFriendsOperation, avatarAId, avatarBId, flags, context)
def removeFriend(self, friendId):
self.runSenderOperation(RemoveFriendOperation, friendId)
def comingOnline(self, avId, friendsList):
self.runServerOperation(ComingOnlineOperation, avId, friendsList)
def goingOffline(self, avId):
self.runServerOperation(GoingOfflineOperation, avId)
def requestSecret(self, requesterId):
print('requestSecret')

View file

@ -19,13 +19,13 @@ class BodyShop(StateData.StateData):
self.legChoice = 0 self.legChoice = 0
self.headChoice = 0 self.headChoice = 0
self.speciesChoice = 0 self.speciesChoice = 0
self.eyelashesChoice = 0
return return
def enter(self, toon, shopsVisited = []): def enter(self, toon, shopsVisited = []):
base.disableMouse() base.disableMouse()
self.toon = toon self.toon = toon
self.dna = self.toon.getStyle() self.dna = self.toon.getStyle()
gender = self.toon.style.getGender()
self.speciesStart = self.getSpeciesStart() self.speciesStart = self.getSpeciesStart()
self.speciesChoice = self.speciesStart self.speciesChoice = self.speciesStart
self.headStart = 0 self.headStart = 0
@ -34,25 +34,24 @@ class BodyShop(StateData.StateData):
self.torsoChoice = ToonDNA.toonTorsoTypes.index(self.dna.torso) % 3 self.torsoChoice = ToonDNA.toonTorsoTypes.index(self.dna.torso) % 3
self.legStart = 0 self.legStart = 0
self.legChoice = ToonDNA.toonLegTypes.index(self.dna.legs) self.legChoice = ToonDNA.toonLegTypes.index(self.dna.legs)
self.eyelashesStart = 0
self.eyelashesChoice = 0
if CLOTHESSHOP in shopsVisited: if CLOTHESSHOP in shopsVisited:
self.clothesPicked = 1 self.clothesPicked = 1
else: else:
self.clothesPicked = 0 self.clothesPicked = 0
self.clothesPicked = 1 self.clothesPicked = 1
if gender == 'm' or ToonDNA.GirlBottoms[self.dna.botTex][1] == ToonDNA.SHORTS:
torsoStyle = 's'
torsoPool = ToonDNA.toonTorsoTypes[:3]
else:
torsoStyle = 'd' torsoStyle = 'd'
torsoPool = ToonDNA.toonTorsoTypes[3:6] torsoPool = ToonDNA.toonTorsoTypes[3:6]
self.__swapSpecies(0) self.__swapSpecies(0)
self.__swapHead(0) self.__swapHead(0)
self.__swapTorso(0) self.__swapTorso(0)
self.__swapLegs(0) self.__swapLegs(0)
choicePool = [ToonDNA.toonHeadTypes, torsoPool, ToonDNA.toonLegTypes] self.__swapEyelashes(0)
choicePool = [ToonDNA.toonHeadTypes, torsoPool, ToonDNA.toonLegTypes, [0, 1]]
self.shuffleButton.setChoicePool(choicePool) self.shuffleButton.setChoicePool(choicePool)
self.accept(self.shuffleFetchMsg, self.changeBody) self.accept(self.shuffleFetchMsg, self.changeBody)
self.acceptOnce('last', self.__handleBackward)
self.accept('next', self.__handleForward) self.accept('next', self.__handleForward)
self.acceptOnce('MAT-newToonCreated', self.shuffleButton.cleanHistory) self.acceptOnce('MAT-newToonCreated', self.shuffleButton.cleanHistory)
self.restrictHeadType(self.dna.head) self.restrictHeadType(self.dna.head)
@ -132,6 +131,44 @@ class BodyShop(StateData.StateData):
shuffleArrowDown, shuffleArrowDown,
shuffleArrowRollover, shuffleArrowRollover,
shuffleArrowDisabled), image_scale=halfButtonInvertScale, image1_scale=halfButtonInvertHoverScale, image2_scale=halfButtonInvertHoverScale, pos=(0.2, 0, 0), command=self.__swapLegs, extraArgs=[1]) shuffleArrowDisabled), image_scale=halfButtonInvertScale, image1_scale=halfButtonInvertHoverScale, image2_scale=halfButtonInvertHoverScale, pos=(0.2, 0, 0), command=self.__swapLegs, extraArgs=[1])
# Creates the eyelashes frame
self.eyelashesFrame = DirectFrame(
parent=self.parentFrame,
image=shuffleFrame,
image_scale=halfButtonInvertScale,
relief=None,
pos=(0, 0, -0.9),
hpr=(0, 0, 3),
scale=0.9,
frameColor=(1, 1, 1, 1),
text=TTLocalizer.BodyShopEyelashes,
text_scale=0.0625,
text_pos=(-0.001, -0.015),
text_fg=(1, 1, 1, 1),
)
self.eyelashesLButton = DirectButton(
parent=self.eyelashesFrame,
relief=None,
image=(shuffleArrowUp, shuffleArrowDown, shuffleArrowRollover, shuffleArrowDisabled),
image_scale=halfButtonScale,
image1_scale=halfButtonHoverScale,
image2_scale=halfButtonHoverScale,
pos=(-0.2, 0, 0),
command=self.__swapEyelashes,
extraArgs=[-1],
)
self.eyelashesRButton = DirectButton(
parent=self.eyelashesFrame,
relief=None,
image=(shuffleArrowUp, shuffleArrowDown, shuffleArrowRollover, shuffleArrowDisabled),
image_scale=halfButtonInvertScale,
image1_scale=halfButtonInvertHoverScale,
image2_scale=halfButtonInvertHoverScale,
pos=(0.2, 0, 0),
command=self.__swapEyelashes,
extraArgs=[1],
)
self.memberButton = DirectButton(relief=None, image=(upsellTex, self.memberButton = DirectButton(relief=None, image=(upsellTex,
upsellTex, upsellTex,
upsellTex, upsellTex,
@ -152,6 +189,7 @@ class BodyShop(StateData.StateData):
self.headFrame.destroy() self.headFrame.destroy()
self.bodyFrame.destroy() self.bodyFrame.destroy()
self.legsFrame.destroy() self.legsFrame.destroy()
self.eyelashesFrame.destroy()
self.speciesLButton.destroy() self.speciesLButton.destroy()
self.speciesRButton.destroy() self.speciesRButton.destroy()
self.headLButton.destroy() self.headLButton.destroy()
@ -160,12 +198,15 @@ class BodyShop(StateData.StateData):
self.torsoRButton.destroy() self.torsoRButton.destroy()
self.legLButton.destroy() self.legLButton.destroy()
self.legRButton.destroy() self.legRButton.destroy()
self.eyelashesLButton.destroy()
self.eyelashesRButton.destroy()
self.memberButton.destroy() self.memberButton.destroy()
del self.parentFrame del self.parentFrame
del self.speciesFrame del self.speciesFrame
del self.headFrame del self.headFrame
del self.bodyFrame del self.bodyFrame
del self.legsFrame del self.legsFrame
del self.eyelashesFrame
del self.speciesLButton del self.speciesLButton
del self.speciesRButton del self.speciesRButton
del self.headLButton del self.headLButton
@ -174,62 +215,43 @@ class BodyShop(StateData.StateData):
del self.torsoRButton del self.torsoRButton
del self.legLButton del self.legLButton
del self.legRButton del self.legRButton
del self.eyelashesLButton
del self.eyelashesRButton
del self.memberButton del self.memberButton
self.shuffleButton.unload() self.shuffleButton.unload()
self.ignore('MAT-newToonCreated') self.ignore('MAT-newToonCreated')
def __swapTorso(self, offset): def __swapTorso(self, offset):
gender = self.toon.style.getGender()
if not self.clothesPicked: if not self.clothesPicked:
length = len(ToonDNA.toonTorsoTypes[6:]) length = len(ToonDNA.toonTorsoTypes[6:])
torsoOffset = 6 torsoOffset = 6
elif gender == 'm':
length = len(ToonDNA.toonTorsoTypes[:3])
torsoOffset = 0
if self.dna.armColor not in ToonDNA.defaultBoyColorList:
self.dna.armColor = ToonDNA.defaultBoyColorList[0]
if self.dna.legColor not in ToonDNA.defaultBoyColorList:
self.dna.legColor = ToonDNA.defaultBoyColorList[0]
if self.dna.headColor not in ToonDNA.defaultBoyColorList:
self.dna.headColor = ToonDNA.defaultBoyColorList[0]
if self.toon.style.topTex not in ToonDNA.MakeAToonBoyShirts:
randomShirt = ToonDNA.getRandomTop(gender, ToonDNA.MAKE_A_TOON)
shirtTex, shirtColor, sleeveTex, sleeveColor = randomShirt
self.toon.style.topTex = shirtTex
self.toon.style.topTexColor = shirtColor
self.toon.style.sleeveTex = sleeveTex
self.toon.style.sleeveTexColor = sleeveColor
if self.toon.style.botTex not in ToonDNA.MakeAToonBoyBottoms:
botTex, botTexColor = ToonDNA.getRandomBottom(gender, ToonDNA.MAKE_A_TOON)
self.toon.style.botTex = botTex
self.toon.style.botTexColor = botTexColor
else:
length = len(ToonDNA.toonTorsoTypes[3:6]) length = len(ToonDNA.toonTorsoTypes[3:6])
if self.toon.style.torso[1] == 'd': if self.toon.style.torso[1] == 'd':
torsoOffset = 3 torsoOffset = 3
else: else:
torsoOffset = 0 torsoOffset = 0
if self.dna.armColor not in ToonDNA.defaultGirlColorList: if self.dna.armColor not in ToonDNA.defaultColorList:
self.dna.armColor = ToonDNA.defaultGirlColorList[0] self.dna.armColor = ToonDNA.defaultColorList[0]
if self.dna.legColor not in ToonDNA.defaultGirlColorList: if self.dna.legColor not in ToonDNA.defaultColorList:
self.dna.legColor = ToonDNA.defaultGirlColorList[0] self.dna.legColor = ToonDNA.defaultColorList[0]
if self.dna.headColor not in ToonDNA.defaultGirlColorList: if self.dna.headColor not in ToonDNA.defaultColorList:
self.dna.headColor = ToonDNA.defaultGirlColorList[0] self.dna.headColor = ToonDNA.defaultColorList[0]
if self.toon.style.topTex not in ToonDNA.MakeAToonGirlShirts: if self.toon.style.topTex not in ToonDNA.MakeAToonShirts:
randomShirt = ToonDNA.getRandomTop(gender, ToonDNA.MAKE_A_TOON) randomShirt = ToonDNA.getRandomTop(ToonDNA.MAKE_A_TOON)
shirtTex, shirtColor, sleeveTex, sleeveColor = randomShirt shirtTex, shirtColor, sleeveTex, sleeveColor = randomShirt
self.toon.style.topTex = shirtTex self.toon.style.topTex = shirtTex
self.toon.style.topTexColor = shirtColor self.toon.style.topTexColor = shirtColor
self.toon.style.sleeveTex = sleeveTex self.toon.style.sleeveTex = sleeveTex
self.toon.style.sleeveTexColor = sleeveColor self.toon.style.sleeveTexColor = sleeveColor
if self.toon.style.botTex not in ToonDNA.MakeAToonGirlBottoms: if self.toon.style.botTex not in ToonDNA.MakeAToonBottoms:
if self.toon.style.torso[1] == 'd': if self.toon.style.torso[1] == 'd':
botTex, botTexColor = ToonDNA.getRandomBottom(gender, ToonDNA.MAKE_A_TOON, girlBottomType=ToonDNA.SKIRT) botTex, botTexColor = ToonDNA.getRandomBottom(ToonDNA.MAKE_A_TOON, girlBottomType=ToonDNA.SKIRT)
self.toon.style.botTex = botTex self.toon.style.botTex = botTex
self.toon.style.botTexColor = botTexColor self.toon.style.botTexColor = botTexColor
torsoOffset = 3 torsoOffset = 3
else: else:
botTex, botTexColor = ToonDNA.getRandomBottom(gender, ToonDNA.MAKE_A_TOON, girlBottomType=ToonDNA.SHORTS) botTex, botTexColor = ToonDNA.getRandomBottom(ToonDNA.MAKE_A_TOON, girlBottomType=ToonDNA.SHORTS)
self.toon.style.botTex = botTex self.toon.style.botTex = botTex
self.toon.style.botTexColor = botTexColor self.toon.style.botTexColor = botTexColor
torsoOffset = 0 torsoOffset = 0
@ -280,6 +302,22 @@ class BodyShop(StateData.StateData):
self.toon.swapToonColor(self.dna) self.toon.swapToonColor(self.dna)
self.restrictHeadType(newHead) self.restrictHeadType(newHead)
def __swapEyelashes(self, offset):
length = len([0,1])
self.eyelashesChoice = (self.eyelashesChoice + offset ) % length
self.__updateScrollButtons(self.eyelashesChoice, length, self.eyelashesStart,
self.eyelashesLButton, self.eyelashesRButton)
self.eyelashes = [0, 1][self.eyelashesChoice]
self.__updateEyelashes()
def __updateEyelashes(self):
self.__updateScrollButtons(self.eyelashesChoice, len([0, 1]), self.eyelashesStart,
self.eyelashesLButton, self.eyelashesRButton)
eyelashesIndex = self.eyelashesChoice
self.dna.eyelashes = eyelashesIndex
self.toon.setEyelashes(eyelashesIndex)
self.toon.setupEyelashes(self.dna)
self.toon.loop("neutral", 0)
def __updateScrollButtons(self, choice, length, start, lButton, rButton): def __updateScrollButtons(self, choice, length, start, lButton, rButton):
if choice == (start - 1) % length: if choice == (start - 1) % length:
rButton['state'] = DGG.DISABLED rButton['state'] = DGG.DISABLED
@ -309,9 +347,7 @@ class BodyShop(StateData.StateData):
self.doneStatus = 'next' self.doneStatus = 'next'
messenger.send(self.doneEvent) messenger.send(self.doneEvent)
def __handleBackward(self):
self.doneStatus = 'last'
messenger.send(self.doneEvent)
def restrictHeadType(self, head): def restrictHeadType(self, head):
if not base.cr.isPaid(): if not base.cr.isPaid():
@ -330,15 +366,18 @@ class BodyShop(StateData.StateData):
newHeadIndex = ToonDNA.toonHeadTypes.index(newHead) - ToonDNA.getHeadStartIndex(ToonDNA.getSpecies(newHead)) newHeadIndex = ToonDNA.toonHeadTypes.index(newHead) - ToonDNA.getHeadStartIndex(ToonDNA.getSpecies(newHead))
newTorsoIndex = ToonDNA.toonTorsoTypes.index(newChoice[1]) newTorsoIndex = ToonDNA.toonTorsoTypes.index(newChoice[1])
newLegsIndex = ToonDNA.toonLegTypes.index(newChoice[2]) newLegsIndex = ToonDNA.toonLegTypes.index(newChoice[2])
newEyelashesIndex = random.randint(0,1)
oldHead = self.toon.style.head oldHead = self.toon.style.head
oldSpeciesIndex = ToonDNA.toonSpeciesTypes.index(ToonDNA.getSpecies(oldHead)) oldSpeciesIndex = ToonDNA.toonSpeciesTypes.index(ToonDNA.getSpecies(oldHead))
oldHeadIndex = ToonDNA.toonHeadTypes.index(oldHead) - ToonDNA.getHeadStartIndex(ToonDNA.getSpecies(oldHead)) oldHeadIndex = ToonDNA.toonHeadTypes.index(oldHead) - ToonDNA.getHeadStartIndex(ToonDNA.getSpecies(oldHead))
oldTorsoIndex = ToonDNA.toonTorsoTypes.index(self.toon.style.torso) oldTorsoIndex = ToonDNA.toonTorsoTypes.index(self.toon.style.torso)
oldLegsIndex = ToonDNA.toonLegTypes.index(self.toon.style.legs) oldLegsIndex = ToonDNA.toonLegTypes.index(self.toon.style.legs)
oldEyelashesIndex = self.toon.style.eyelashes
self.__swapSpecies(newSpeciesIndex - oldSpeciesIndex) self.__swapSpecies(newSpeciesIndex - oldSpeciesIndex)
self.__swapHead(newHeadIndex - oldHeadIndex) self.__swapHead(newHeadIndex - oldHeadIndex)
self.__swapTorso(newTorsoIndex - oldTorsoIndex) self.__swapTorso(newTorsoIndex - oldTorsoIndex)
self.__swapLegs(newLegsIndex - oldLegsIndex) self.__swapLegs(newLegsIndex - oldLegsIndex)
self.__swapEyelashes(newEyelashesIndex - oldEyelashesIndex)
def getCurrToonSetting(self): def getCurrToonSetting(self):
return [self.toon.style.head, self.toon.style.torso, self.toon.style.legs] return [self.toon.style.head, self.toon.style.torso, self.toon.style.legs]

View file

@ -20,8 +20,7 @@ class ClothesGUI(StateData.StateData):
self.type = type self.type = type
self.toon = None self.toon = None
self.swapEvent = swapEvent self.swapEvent = swapEvent
self.gender = '?' self.toonInShorts = 0
self.girlInShorts = 0
self.swappedTorso = 0 self.swappedTorso = 0
return return
@ -119,17 +118,13 @@ class ClothesGUI(StateData.StateData):
self.ignore(self.shuffleFetchMsg) self.ignore(self.shuffleFetchMsg)
def setupButtons(self): def setupButtons(self):
self.girlInShorts = 0 self.toonInShorts = 0
if self.gender == 'f':
if self.bottomChoice == -1: if self.bottomChoice == -1:
botTex = self.bottoms[0][0] botTex = self.bottoms[0][0]
else: else:
botTex = self.bottoms[self.bottomChoice][0] botTex = self.bottoms[self.bottomChoice][0]
if ToonDNA.GirlBottoms[botTex][1] == ToonDNA.SHORTS: if ToonDNA.Bottoms[botTex][1] == ToonDNA.SHORTS:
self.girlInShorts = 1 self.toonInShorts = 1
if self.toon.style.getGender() == 'm':
self.bottomFrame['text'] = TTLocalizer.ClothesShopShorts
else:
self.bottomFrame['text'] = TTLocalizer.ClothesShopBottoms self.bottomFrame['text'] = TTLocalizer.ClothesShopBottoms
self.acceptOnce('last', self.__handleBackward) self.acceptOnce('last', self.__handleBackward)
self.acceptOnce('next', self.__handleForward) self.acceptOnce('next', self.__handleForward)

View file

@ -18,17 +18,15 @@ class ColorShop(StateData.StateData):
self.colorAll = 1 self.colorAll = 1
return return
def getGenderColorList(self, dna): def getColorList(self, dna):
if self.dna.getGender() == 'm': return ToonDNA.defaultColorList
return ToonDNA.defaultBoyColorList
else:
return ToonDNA.defaultGirlColorList
def enter(self, toon, shopsVisited = []): def enter(self, toon, shopsVisited = []):
base.disableMouse() base.disableMouse()
self.toon = toon self.toon = toon
self.dna = toon.getStyle() self.dna = toon.getStyle()
colorList = self.getGenderColorList(self.dna) colorList = self.getColorList(self.dna)
try: try:
self.headChoice = colorList.index(self.dna.headColor) self.headChoice = colorList.index(self.dna.headColor)
self.armChoice = colorList.index(self.dna.armColor) self.armChoice = colorList.index(self.dna.armColor)
@ -44,7 +42,7 @@ class ColorShop(StateData.StateData):
self.startColor = 0 self.startColor = 0
self.acceptOnce('last', self.__handleBackward) self.acceptOnce('last', self.__handleBackward)
self.acceptOnce('next', self.__handleForward) self.acceptOnce('next', self.__handleForward)
choicePool = [self.getGenderColorList(self.dna), self.getGenderColorList(self.dna), self.getGenderColorList(self.dna)] choicePool = [self.getColorList(self.dna), self.getColorList(self.dna), self.getColorList(self.dna)]
self.shuffleButton.setChoicePool(choicePool) self.shuffleButton.setChoicePool(choicePool)
self.accept(self.shuffleFetchMsg, self.changeColor) self.accept(self.shuffleFetchMsg, self.changeColor)
self.acceptOnce('MAT-newToonCreated', self.shuffleButton.cleanHistory) self.acceptOnce('MAT-newToonCreated', self.shuffleButton.cleanHistory)
@ -153,7 +151,7 @@ class ColorShop(StateData.StateData):
self.ignore('MAT-newToonCreated') self.ignore('MAT-newToonCreated')
def __swapAllColor(self, offset): def __swapAllColor(self, offset):
colorList = self.getGenderColorList(self.dna) colorList = self.getColorList(self.dna)
length = len(colorList) length = len(colorList)
choice = (self.headChoice + offset) % length choice = (self.headChoice + offset) % length
self.__updateScrollButtons(choice, length, self.allLButton, self.allRButton) self.__updateScrollButtons(choice, length, self.allLButton, self.allRButton)
@ -164,7 +162,7 @@ class ColorShop(StateData.StateData):
self.__swapLegColor(choice - oldLegColorIndex) self.__swapLegColor(choice - oldLegColorIndex)
def __swapHeadColor(self, offset): def __swapHeadColor(self, offset):
colorList = self.getGenderColorList(self.dna) colorList = self.getColorList(self.dna)
length = len(colorList) length = len(colorList)
self.headChoice = (self.headChoice + offset) % length self.headChoice = (self.headChoice + offset) % length
self.__updateScrollButtons(self.headChoice, length, self.headLButton, self.headRButton) self.__updateScrollButtons(self.headChoice, length, self.headLButton, self.headRButton)
@ -173,7 +171,7 @@ class ColorShop(StateData.StateData):
self.toon.swapToonColor(self.dna) self.toon.swapToonColor(self.dna)
def __swapArmColor(self, offset): def __swapArmColor(self, offset):
colorList = self.getGenderColorList(self.dna) colorList = self.getColorList(self.dna)
length = len(colorList) length = len(colorList)
self.armChoice = (self.armChoice + offset) % length self.armChoice = (self.armChoice + offset) % length
self.__updateScrollButtons(self.armChoice, length, self.armLButton, self.armRButton) self.__updateScrollButtons(self.armChoice, length, self.armLButton, self.armRButton)
@ -182,7 +180,7 @@ class ColorShop(StateData.StateData):
self.toon.swapToonColor(self.dna) self.toon.swapToonColor(self.dna)
def __swapLegColor(self, offset): def __swapLegColor(self, offset):
colorList = self.getGenderColorList(self.dna) colorList = self.getColorList(self.dna)
length = len(colorList) length = len(colorList)
self.legChoice = (self.legChoice + offset) % length self.legChoice = (self.legChoice + offset) % length
self.__updateScrollButtons(self.legChoice, length, self.legLButton, self.legRButton) self.__updateScrollButtons(self.legChoice, length, self.legLButton, self.legRButton)
@ -210,7 +208,7 @@ class ColorShop(StateData.StateData):
def changeColor(self): def changeColor(self):
self.notify.debug('Entering changeColor') self.notify.debug('Entering changeColor')
colorList = self.getGenderColorList(self.dna) colorList = self.getColorList(self.dna)
newChoice = self.shuffleButton.getCurrChoice() newChoice = self.shuffleButton.getCurrChoice()
newHeadColorIndex = colorList.index(newChoice[0]) newHeadColorIndex = colorList.index(newChoice[0])
newArmColorIndex = colorList.index(newChoice[1]) newArmColorIndex = colorList.index(newChoice[1])

View file

@ -18,7 +18,6 @@ from .MakeAToonGlobals import *
from direct.interval.IntervalGlobal import * from direct.interval.IntervalGlobal import *
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.toontowngui import TTDialog from toontown.toontowngui import TTDialog
from . import GenderShop
from . import BodyShop from . import BodyShop
from . import ColorShop from . import ColorShop
from . import MakeClothesGUI from . import MakeClothesGUI
@ -53,24 +52,22 @@ class MakeAToon(StateData.StateData):
self.namelessPotAv = av self.namelessPotAv = av
self.nameList.append(av.name) self.nameList.append(av.name)
self.fsm = ClassicFSM.ClassicFSM('MakeAToon', [State.State('Init', self.enterInit, self.exitInit, ['GenderShop', 'NameShop']), self.fsm = ClassicFSM.ClassicFSM('MakeAToon', [State.State('Init', self.enterInit, self.exitInit, ['BodyShop', 'NameShop']),
State.State('GenderShop', self.enterGenderShop, self.exitGenderShop, ['BodyShop']), State.State('BodyShop', self.enterBodyShop, self.exitBodyShop, ['ColorShop']),
State.State('BodyShop', self.enterBodyShop, self.exitBodyShop, ['GenderShop', 'ColorShop']),
State.State('ColorShop', self.enterColorShop, self.exitColorShop, ['BodyShop', 'ClothesShop']), State.State('ColorShop', self.enterColorShop, self.exitColorShop, ['BodyShop', 'ClothesShop']),
State.State('ClothesShop', self.enterClothesShop, self.exitClothesShop, ['ColorShop', 'NameShop']), State.State('ClothesShop', self.enterClothesShop, self.exitClothesShop, ['ColorShop', 'NameShop']),
State.State('NameShop', self.enterNameShop, self.exitNameShop, ['ClothesShop']), State.State('NameShop', self.enterNameShop, self.exitNameShop, ['ClothesShop']),
State.State('Done', self.enterDone, self.exitDone, [])], 'Init', 'Done') State.State('Done', self.enterDone, self.exitDone, [])], 'Init', 'Done')
self.parentFSM = parentFSM self.parentFSM = parentFSM
self.parentFSM.getStateNamed('createAvatar').addChild(self.fsm) self.parentFSM.getStateNamed('createAvatar').addChild(self.fsm)
self.gs = GenderShop.GenderShop(self, 'GenderShop-done')
self.bs = BodyShop.BodyShop('BodyShop-done') self.bs = BodyShop.BodyShop('BodyShop-done')
self.cos = ColorShop.ColorShop('ColorShop-done') self.cos = ColorShop.ColorShop('ColorShop-done')
self.cls = MakeClothesGUI.MakeClothesGUI('ClothesShop-done') self.cls = MakeClothesGUI.MakeClothesGUI('ClothesShop-done')
self.ns = NameShop.NameShop(self, 'NameShop-done', avList, index, self.isPaid) self.ns = NameShop.NameShop(self, 'NameShop-done', avList, index, self.isPaid)
self.shop = GENDERSHOP self.shop = BODYSHOP
self.shopsVisited = [] self.shopsVisited = []
if self.warp: if self.warp:
self.shopsVisited = [GENDERSHOP, self.shopsVisited = [
BODYSHOP, BODYSHOP,
COLORSHOP, COLORSHOP,
CLOTHESSHOP] CLOTHESSHOP]
@ -98,10 +95,6 @@ class MakeAToon(StateData.StateData):
base.playMusic(self.music, looping=1, volume=self.musicVolume) base.playMusic(self.music, looping=1, volume=self.musicVolume)
camera.setPosHpr(-5.7, -12.3501, 2.15, -24.8499, 2.73, 0) camera.setPosHpr(-5.7, -12.3501, 2.15, -24.8499, 2.73, 0)
if self.warp: if self.warp:
if self.toon.style.torso[1] == 's':
self.toon.gender = 's'
else:
self.toon.gender = 'd'
self.toon.reparentTo(render) self.toon.reparentTo(render)
self.toon.loop('neutral') self.toon.loop('neutral')
self.toon.setPosHpr(-4.1, -2, 0, 200, 0, 0) self.toon.setPosHpr(-4.1, -2, 0, 200, 0, 0)
@ -113,7 +106,7 @@ class MakeAToon(StateData.StateData):
self.guiLastButton.hide() self.guiLastButton.hide()
self.fsm.request('NameShop') self.fsm.request('NameShop')
else: else:
self.fsm.request('GenderShop') self.fsm.request('BodyShop')
def exit(self): def exit(self):
base.camLens.setFov(ToontownGlobals.DefaultCameraFov) base.camLens.setFov(ToontownGlobals.DefaultCameraFov)
@ -206,8 +199,6 @@ class MakeAToon(StateData.StateData):
ee.bind(DGG.B1PRESS, lambda x, ee = ee: self.toggleSlide()) ee.bind(DGG.B1PRESS, lambda x, ee = ee: self.toggleSlide())
self.eee = ee self.eee = ee
self.room = loader.loadModel('phase_3/models/makeatoon/tt_m_ara_mat_room') self.room = loader.loadModel('phase_3/models/makeatoon/tt_m_ara_mat_room')
self.genderWalls = self.room.find('**/genderWalls')
self.genderProps = self.room.find('**/genderProps')
self.bodyWalls = self.room.find('**/bodyWalls') self.bodyWalls = self.room.find('**/bodyWalls')
self.bodyProps = self.room.find('**/bodyProps') self.bodyProps = self.room.find('**/bodyProps')
self.colorWalls = self.room.find('**/colorWalls') self.colorWalls = self.room.find('**/colorWalls')
@ -246,7 +237,6 @@ class MakeAToon(StateData.StateData):
self.toon.setNameVisible(0) self.toon.setNameVisible(0)
self.toon.startBlink() self.toon.startBlink()
self.toon.startLookAround() self.toon.startLookAround()
self.gs.load()
self.bs.load() self.bs.load()
self.cos.load() self.cos.load()
self.cls.load() self.cls.load()
@ -268,12 +258,10 @@ class MakeAToon(StateData.StateData):
if self.toon: if self.toon:
self.toon.stopBlink() self.toon.stopBlink()
self.toon.stopLookAroundNow() self.toon.stopLookAroundNow()
self.gs.unload()
self.bs.unload() self.bs.unload()
self.cos.unload() self.cos.unload()
self.cls.unload() self.cls.unload()
self.ns.unload() self.ns.unload()
del self.gs
del self.bs del self.bs
del self.cos del self.cos
del self.cls del self.cls
@ -312,10 +300,6 @@ class MakeAToon(StateData.StateData):
self.cleanupFocusOutIval() self.cleanupFocusOutIval()
self.room.removeNode() self.room.removeNode()
del self.room del self.room
self.genderWalls.removeNode()
self.genderProps.removeNode()
del self.genderWalls
del self.genderProps
self.bodyWalls.removeNode() self.bodyWalls.removeNode()
self.bodyProps.removeNode() self.bodyProps.removeNode()
del self.bodyWalls del self.bodyWalls
@ -377,9 +361,7 @@ class MakeAToon(StateData.StateData):
def goToNextShop(self): def goToNextShop(self):
self.progressing = 1 self.progressing = 1
if self.shop == GENDERSHOP: if self.shop == BODYSHOP:
self.fsm.request('BodyShop')
elif self.shop == BODYSHOP:
self.fsm.request('ColorShop') self.fsm.request('ColorShop')
elif self.shop == COLORSHOP: elif self.shop == COLORSHOP:
self.fsm.request('ClothesShop') self.fsm.request('ClothesShop')
@ -388,9 +370,7 @@ class MakeAToon(StateData.StateData):
def goToLastShop(self): def goToLastShop(self):
self.progressing = 0 self.progressing = 0
if self.shop == BODYSHOP: if self.shop == COLORSHOP:
self.fsm.request('GenderShop')
elif self.shop == COLORSHOP:
self.fsm.request('BodyShop') self.fsm.request('BodyShop')
elif self.shop == CLOTHESSHOP: elif self.shop == CLOTHESSHOP:
self.fsm.request('ColorShop') self.fsm.request('ColorShop')
@ -408,60 +388,63 @@ class MakeAToon(StateData.StateData):
def exitInit(self): def exitInit(self):
pass pass
def enterGenderShop(self):
base.cr.centralLogger.writeClientEvent('MAT - enteringGenderShop')
self.shop = GENDERSHOP
if GENDERSHOP not in self.shopsVisited:
self.shopsVisited.append(GENDERSHOP)
self.genderWalls.reparentTo(self.squishJoint)
self.genderProps.reparentTo(self.propJoint)
self.roomSquishActor.pose('squish', 0)
self.guiNextButton['state'] = DGG.DISABLED
else:
self.dropRoom(self.genderWalls, self.genderProps)
self.guiTopBar['text'] = TTLocalizer.CreateYourToonTitle
self.guiTopBar['text_fg'] = (1, 0.92, 0.2, 1)
self.guiTopBar['text_scale'] = TTLocalizer.MATenterGenderShop
base.transitions.fadeIn()
self.accept('GenderShop-done', self.__handleGenderShopDone)
self.gs.enter()
self.guiNextButton.show()
self.gs.showButtons()
self.rotateLeftButton.hide()
self.rotateRightButton.hide()
def exitGenderShop(self):
self.squishRoom(self.genderWalls)
self.squishProp(self.genderProps)
self.gs.exit()
self.ignore('GenderShop-done')
def __handleGenderShopDone(self):
self.guiNextButton.hide()
self.gs.hideButtons()
self.goToNextShop()
def bodyShopOpening(self): def bodyShopOpening(self):
self.bs.showButtons() self.bs.showButtons()
self.guiNextButton.show() self.guiNextButton.show()
self.guiLastButton.show()
self.rotateLeftButton.show() self.rotateLeftButton.show()
self.rotateRightButton.show() self.rotateRightButton.show()
def enterBodyShop(self): def enterBodyShop(self):
if hasattr(self, 'toon'):
if self.toon:
self.toon.show()
else:
self.dna = ToonDNA.ToonDNA()
# stage = 1 is MAKE_A_TOON
self.dna.newToonRandom(eyelashes=random.randint(0, 1), stage=1)
self.toon = Toon.Toon()
self.toon.setDNA(self.dna)
# make sure the avatar uses its highest LOD
self.toon.useLOD(1000)
# make sure his name doesn't show up
self.toon.setNameVisible(0)
self.toon.startBlink()
self.toon.startLookAround()
self.toon.reparentTo(render)
self.toon.setPos(self.toonPosition)
self.toon.setHpr(self.toonHpr)
self.toon.setScale(self.toonScale)
self.toon.loop("neutral")
self.setNextButtonState(DGG.NORMAL)
self.setToon(self.toon)
messenger.send("MAT-newToonCreated")
base.cr.centralLogger.writeClientEvent('MAT - enteringBodyShop') base.cr.centralLogger.writeClientEvent('MAT - enteringBodyShop')
if self.toon:
self.toon.show() self.toon.show()
self.shop = BODYSHOP self.shop = BODYSHOP
self.guiTopBar['text'] = TTLocalizer.ShapeYourToonTitle self.guiTopBar['text'] = TTLocalizer.ShapeYourToonTitle
self.guiTopBar['text_fg'] = (0.0, 0.98, 0.5, 1) self.guiTopBar['text_fg'] = (0.0, 0.98, 0.5, 1)
self.guiTopBar['text_scale'] = TTLocalizer.MATenterBodyShop self.guiTopBar['text_scale'] = TTLocalizer.MATenterBodyShop
self.accept('BodyShop-done', self.__handleBodyShopDone) self.accept("BodyShop-done", self.__handleBodyShopDone)
self.dropRoom(self.bodyWalls, self.bodyProps) self.dropRoom(self.bodyWalls, self.bodyProps)
# we need to do this first for setup...
self.bs.enter(self.toon, self.shopsVisited) self.bs.enter(self.toon, self.shopsVisited)
if BODYSHOP not in self.shopsVisited:
if (BODYSHOP not in self.shopsVisited):
self.shopsVisited.append(BODYSHOP) self.shopsVisited.append(BODYSHOP)
self.bodyShopOpening() self.bodyShopOpening()
def exitBodyShop(self): def exitBodyShop(self):
self.squishRoom(self.bodyWalls) self.squishRoom(self.bodyWalls)
self.squishProp(self.bodyProps) self.squishProp(self.bodyProps)

View file

@ -1,5 +1,4 @@
GENDERSHOP = 1 BODYSHOP = 1
BODYSHOP = 2
COLORSHOP = 3 COLORSHOP = 3
CLOTHESSHOP = 4 CLOTHESSHOP = 4
NAMESHOP = 5 NAMESHOP = 5

View file

@ -9,11 +9,8 @@ class MakeClothesGUI(ClothesGUI.ClothesGUI):
def setupScrollInterface(self): def setupScrollInterface(self):
self.dna = self.toon.getStyle() self.dna = self.toon.getStyle()
gender = self.dna.getGender() self.tops = ToonDNA.getRandomizedTops(tailorId = ToonDNA.MAKE_A_TOON)
if gender != self.gender: self.bottoms = ToonDNA.getRandomizedBottoms(tailorId = ToonDNA.MAKE_A_TOON)
self.tops = ToonDNA.getRandomizedTops(gender, tailorId=ToonDNA.MAKE_A_TOON)
self.bottoms = ToonDNA.getRandomizedBottoms(gender, tailorId=ToonDNA.MAKE_A_TOON)
self.gender = gender
self.topChoice = 0 self.topChoice = 0
self.bottomChoice = 0 self.bottomChoice = 0
self.setupButtons() self.setupButtons()
@ -21,9 +18,8 @@ class MakeClothesGUI(ClothesGUI.ClothesGUI):
def setupButtons(self): def setupButtons(self):
ClothesGUI.ClothesGUI.setupButtons(self) ClothesGUI.ClothesGUI.setupButtons(self)
if len(self.dna.torso) == 1: if len(self.dna.torso) == 1:
if self.gender == 'm':
torsoStyle = 's' if self.toonInShorts == 1:
elif self.girlInShorts == 1:
torsoStyle = 's' torsoStyle = 's'
else: else:
torsoStyle = 'd' torsoStyle = 'd'

View file

@ -1,16 +1,22 @@
from panda3d.core import * from pandac.PandaModules import *
import random import random
import string import string
import copy import copy
from toontown.toonbase import ToontownGlobals from toontown.toonbase import ToontownGlobals
from toontown.toonbase import TTLocalizer from toontown.toonbase import TTLocalizer
import os import os
from direct.showbase import AppRunnerGlobal
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
class NameGenerator: class NameGenerator:
# A TextNode we will use to measure the lengths of names
text = TextNode('text') text = TextNode('text')
text.setFont(ToontownGlobals.getInterfaceFont()) text.setFont(ToontownGlobals.getInterfaceFont())
notify = DirectNotifyGlobal.directNotify.newCategory('NameGenerator')
notify = DirectNotifyGlobal.directNotify.newCategory("NameGenerator")
boyTitles = [] boyTitles = []
girlTitles = [] girlTitles = []
neutralTitles = [] neutralTitles = []
@ -23,8 +29,13 @@ class NameGenerator:
def __init__(self): def __init__(self):
self.generateLists() self.generateLists()
return
def generateLists(self): def generateLists(self):
""" This method looks in a text file specified in the localizer and loads
in all the names into the 8 lists as well as populating self.nameDictionary
which has uniqueIDs mapped to a tuple of category and name
"""
self.boyTitles = [] self.boyTitles = []
self.girlTitles = [] self.girlTitles = []
self.neutralTitles = [] self.neutralTitles = []
@ -35,80 +46,92 @@ class NameGenerator:
self.lastPrefixes = [] self.lastPrefixes = []
self.lastSuffixes = [] self.lastSuffixes = []
self.nameDictionary = {} self.nameDictionary = {}
# Look for the name master file and read it in.
searchPath = DSearchPath() searchPath = DSearchPath()
if __debug__: if __debug__:
searchPath.appendDirectory(Filename('resources/phase_3/etc')) searchPath.appendDirectory(Filename('resources/phase_3/etc'))
if AppRunnerGlobal.appRunner:
# In the web-publish runtime, it will always be here:
searchPath.appendDirectory(Filename.expandFrom('$TT_3_ROOT/phase_3/etc'))
else:
# In other environments, including the dev environment, look here:
searchPath.appendDirectory(Filename('phase_3/etc'))
base = os.path.expandvars('$TOONTOWN') or './toontown'
searchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars(base + '/src/configfiles')))
# RobotToonManager needs to look for file in current directory
searchPath.appendDirectory(Filename('.'))
filename = Filename(TTLocalizer.NameShopNameMaster) filename = Filename(TTLocalizer.NameShopNameMaster)
found = vfs.resolveFilename(filename, searchPath) found = vfs.resolveFilename(filename, searchPath)
if not found: if not found:
self.notify.error("NameGenerator: Error opening name list text file '%s.'" % TTLocalizer.NameShopNameMaster) self.notify.error("NameGenerator: Error opening name list text file '%s.'" % TTLocalizer.NameShopNameMaster)
input = StreamReader(vfs.openReadFile(filename, 1), 1) input = StreamReader(vfs.openReadFile(filename, 1), 1)
currentLine = input.readline() currentLine = input.readline()
while currentLine: while currentLine:
if currentLine.lstrip()[0:1] != b'#': if currentLine.lstrip()[0:1] != b'#':
a1 = currentLine.find(b'*') a1 = currentLine.find(b'*')
a2 = currentLine.find(b'*', a1 + 1) a2 = currentLine.find(b'*', a1 + 1)
self.nameDictionary[int(currentLine[0:a1])] = (int(currentLine[a1 + 1:a2]), currentLine[a2 + 1:].rstrip().decode('utf-8')) self.nameDictionary[int(currentLine[0:a1])] = (int(currentLine[a1 + 1:a2]),
currentLine[a2 + 1:].rstrip().decode('utf-8'))
currentLine = input.readline() currentLine = input.readline()
masterList = [self.boyTitles, masterList = [self.boyTitles, self.girlTitles, self.neutralTitles,
self.girlTitles, self.boyFirsts, self.girlFirsts, self.neutralFirsts,
self.neutralTitles, self.capPrefixes, self.lastPrefixes, self.lastSuffixes]
self.boyFirsts,
self.girlFirsts,
self.neutralFirsts,
self.capPrefixes,
self.lastPrefixes,
self.lastSuffixes]
for tu in list(self.nameDictionary.values()): for tu in list(self.nameDictionary.values()):
masterList[tu[0]].append(tu[1]) masterList[tu[0]].append(tu[1])
return 1 return 1
def _getNameParts(self, cat2part): def _getNameParts(self, cat2part):
nameParts = [{}, # returns list of dict of string->index, one dict per name part
{}, nameParts = [{}, {}, {}, {}]
{}, # cat2part is mapping of NameMasterEnglish.txt category -> namePart index
{}]
for id, tpl in self.nameDictionary.items(): for id, tpl in self.nameDictionary.items():
cat, str = tpl cat, str = tpl
if cat in cat2part: if cat in cat2part:
nameParts[cat2part[cat]][str] = id nameParts[cat2part[cat]][str] = id
return nameParts return nameParts
def getMaleNameParts(self): def getAllNameParts(self):
return self._getNameParts({0: 0, return self._getNameParts({0: 0,
1: 0,
2: 0, 2: 0,
3: 1, 3: 1,
5: 1,
6: 2,
7: 2,
8: 3})
def getFemaleNameParts(self):
return self._getNameParts({1: 0,
2: 0,
4: 1, 4: 1,
5: 1, 5: 1,
6: 2, 6: 2,
7: 2, 7: 2,
8: 3}) 8: 3,
})
def getLastNamePrefixesCapped(self): def getLastNamePrefixesCapped(self):
return self.capPrefixes return self.capPrefixes
def returnUniqueID(self, name, listnumber): def returnUniqueID(self, name, listnumber):
""" This is a helper function which accepts a name string, and a listnumber of
type 0 = title, 1 = firstname, 2 = prefix, 3 = suffix
It then makes a list of search tuples newtu and searches nameDictionary.
If successful it returns the uniqueID, if not then -1
"""
newtu = [(), (), ()] newtu = [(), (), ()]
if listnumber == 0: if listnumber == 0:
# Looking for a title
newtu[0] = (0, name) newtu[0] = (0, name)
newtu[1] = (1, name) newtu[1] = (1, name)
newtu[2] = (2, name) newtu[2] = (2, name)
elif listnumber == 1: elif listnumber == 1:
# Looking for a first name
newtu[0] = (3, name) newtu[0] = (3, name)
newtu[1] = (4, name) newtu[1] = (4, name)
newtu[2] = (5, name) newtu[2] = (5, name)
elif listnumber == 2: elif listnumber == 2:
# Looking for a prefix
newtu[0] = (6, name) newtu[0] = (6, name)
newtu[1] = (7, name) newtu[1] = (7, name)
else: else:
@ -117,60 +140,99 @@ class NameGenerator:
for g in newtu: for g in newtu:
if tu[1] == g: if tu[1] == g:
return tu[0] return tu[0]
return -1 return -1
def findWidestInList(self, text, nameList): def findWidestInList(self, text, nameList):
maxWidth = 0 maxWidth = 0
maxName = '' maxName = ""
for name in nameList: for name in nameList:
width = text.calcWidth(name) width = text.calcWidth(name)
if width > maxWidth: if width > maxWidth:
maxWidth = text.calcWidth(name) maxWidth = text.calcWidth(name)
maxName = name maxName = name
print(maxName + " " + str(maxWidth))
print(maxName + ' ' + str(maxWidth))
return maxName return maxName
def findWidestName(self): def findWidestName(self):
longestBoyTitle = self.findWidestInList(self.text, self.boyTitles + self.neutralTitles) longestBoyTitle = self.findWidestInList(self.text,
longestGirlTitle = self.findWidestInList(self.text, self.girlTitles + self.neutralTitles) self.boyTitles +
longestBoyFirst = self.findWidestInList(self.text, self.boyFirsts + self.neutralFirsts) self.neutralTitles)
longestGirlFirst = self.findWidestInList(self.text, self.girlFirsts + self.neutralFirsts) longestGirlTitle = self.findWidestInList(self.text,
longestLastPrefix = self.findWidestInList(self.text, self.lastPrefixes) self.girlTitles +
longestLastSuffix = self.findWidestInList(self.text, self.lastSuffixes) self.neutralTitles)
longestBoyFront = self.findWidestInList(self.text, [longestBoyTitle, longestBoyFirst]) longestBoyFirst = self.findWidestInList(self.text,
longestGirlFront = self.findWidestInList(self.text, [longestGirlTitle, longestGirlFirst]) self.boyFirsts +
longestBoyName = longestBoyTitle + ' ' + longestBoyFirst + ' ' + longestLastPrefix + longestLastSuffix self.neutralFirsts)
longestGirlName = longestGirlTitle + ' ' + longestGirlFirst + ' ' + longestLastPrefix + longestLastSuffix longestGirlFirst = self.findWidestInList(self.text,
longestName = self.findWidestInList(self.text, [longestBoyName, longestGirlName]) self.girlFirsts +
self.neutralFirsts)
longestLastPrefix = self.findWidestInList(self.text,
self.lastPrefixes)
longestLastSuffix = self.findWidestInList(self.text,
self.lastSuffixes)
longestBoyFront = self.findWidestInList(self.text,
[longestBoyTitle,
longestBoyFirst])
longestGirlFront = self.findWidestInList(self.text,
[longestGirlTitle,
longestGirlFirst])
longestBoyName = (longestBoyTitle + " " + longestBoyFirst + " " +
longestLastPrefix + longestLastSuffix)
longestGirlName = (longestGirlTitle + " " + longestGirlFirst + " " +
longestLastPrefix + longestLastSuffix)
longestName = self.findWidestInList(self.text,
[longestBoyName, longestGirlName])
return longestName return longestName
def findWidestTitleFirst(self): def findWidestTitleFirst(self):
longestBoyTitle = self.findWidestInList(self.text, self.boyTitles + self.neutralTitles) longestBoyTitle = self.findWidestInList(self.text,
longestGirlTitle = self.findWidestInList(self.text, self.girlTitles + self.neutralTitles) self.boyTitles +
longestBoyFirst = self.findWidestInList(self.text, self.boyFirsts + self.neutralFirsts) self.neutralTitles)
longestGirlFirst = self.findWidestInList(self.text, self.girlFirsts + self.neutralFirsts) longestGirlTitle = self.findWidestInList(self.text,
longestBoyName = longestBoyTitle + ' ' + longestBoyFirst self.girlTitles +
longestGirlName = longestGirlTitle + ' ' + longestGirlFirst self.neutralTitles)
longestName = self.findWidestInList(self.text, [longestBoyName, longestGirlName]) longestBoyFirst = self.findWidestInList(self.text,
self.boyFirsts +
self.neutralFirsts)
longestGirlFirst = self.findWidestInList(self.text,
self.girlFirsts +
self.neutralFirsts)
longestBoyName = (longestBoyTitle + " " + longestBoyFirst)
longestGirlName = (longestGirlTitle + " " + longestGirlFirst)
longestName = self.findWidestInList(self.text,
[longestBoyName, longestGirlName])
def findWidestTitle(self): def findWidestTitle(self):
widestTitle = self.findWidestInList(self.text, self.neutralTitles + self.boyTitles + self.girlTitles) widestTitle = self.findWidestInList(self.text,
self.neutralTitles +
self.boyTitles +
self.girlTitles)
return widestTitle return widestTitle
def findWidestFirstName(self): def findWidestFirstName(self):
widestFirst = self.findWidestInList(self.text, self.neutralFirsts + self.boyFirsts + self.girlFirsts) widestFirst = self.findWidestInList(self.text,
self.neutralFirsts +
self.boyFirsts +
self.girlFirsts)
return widestFirst return widestFirst
def findWidestLastName(self): def findWidestLastName(self):
longestLastPrefix = self.findWidestInList(self.text, self.lastPrefixes) longestLastPrefix = self.findWidestInList(self.text,
longestLastSuffix = self.findWidestInList(self.text, self.lastSuffixes) self.lastPrefixes)
longestLastSuffix = self.findWidestInList(self.text,
self.lastSuffixes)
longestLastName = longestLastPrefix + longestLastSuffix longestLastName = longestLastPrefix + longestLastSuffix
return longestLastName return longestLastName
def findWidestNameWord(self): def findWidestNameWord(self):
widestWord = self.findWidestInList(self.text, [self.findWidestTitle(), self.findWidestFirstName(), self.findWidestLastName()]) widestWord = self.findWidestInList(self.text,
[self.findWidestTitle(),
self.findWidestFirstName(),
self.findWidestLastName()])
return widestWord return widestWord
def findWidestNameWidth(self): def findWidestNameWidth(self):
@ -181,126 +243,168 @@ class NameGenerator:
name = self.findWidestName() name = self.findWidestName()
width = self.text.calcWidth(name) width = self.text.calcWidth(name)
widthStr = str(width) widthStr = str(width)
print('The widest name is: ' + name + ' (' + widthStr + ' units)') print(("The widest name is: " + name + " (" +
widthStr + " units)"))
def printWidestLastName(self): def printWidestLastName(self):
name = self.findWidestLastName() name = self.findWidestLastName()
width = self.text.calcWidth(name) width = self.text.calcWidth(name)
widthStr = str(width) widthStr = str(width)
print('The widest last name is: ' + name + ' (' + widthStr + ' units)') print(("The widest last name is: " + name + " (" +
widthStr + " units)"))
def randomName(self, boy=0, girl=0): def randomName(self, boy=0, girl=0):
""" This method is outdated for current uses in Toontown, but good for
general debugging. You probably want to use randomNameMoreinfo
"""
if boy and girl: if boy and girl:
self.error("A name can't be both boy and girl!") self.error("A name can't be both boy and girl!")
if not boy and not girl:
if (not boy) and (not girl):
# Randomly pick the name sex
boy = random.choice([0, 1]) boy = random.choice([0, 1])
girl = not boy girl = not boy
uberFlag = random.choice(['title-first',
'title-last', # Five types of name combos
'first', uberFlag = random.choice(["title-first", "title-last",
'last', "first", "last", "first-last", "title-first-last"])
'first-last',
'title-first-last'])
titleFlag = 0 titleFlag = 0
if uberFlag == 'title-first' or uberFlag == 'title-last' or uberFlag == 'title-first-last': if ((uberFlag == "title-first") or
(uberFlag == "title-last") or
(uberFlag == "title-first-last")):
titleFlag = 1 titleFlag = 1
firstFlag = 0 firstFlag = 0
if uberFlag == 'title-first' or uberFlag == 'first' or uberFlag == 'first-last' or uberFlag == 'title-first-last': if ((uberFlag == "title-first") or
(uberFlag == "first") or
(uberFlag == "first-last") or
(uberFlag == "title-first-last")):
firstFlag = 1 firstFlag = 1
lastFlag = 0 lastFlag = 0
if uberFlag == 'title-last' or uberFlag == 'last' or uberFlag == 'first-last' or uberFlag == 'title-first-last': if ((uberFlag == "title-last") or
(uberFlag == "last") or
(uberFlag == "first-last") or
(uberFlag == "title-first-last")
):
lastFlag = 1 lastFlag = 1
retString = ''
retString = ""
if titleFlag: if titleFlag:
# Shallow copy, since we will be altering the list
titleList = self.neutralTitles[:] titleList = self.neutralTitles[:]
if boy: if boy:
titleList += self.boyTitles titleList += self.boyTitles
elif girl: elif girl:
titleList += self.girlTitles titleList += self.girlTitles
else: else:
self.error('Must be boy or girl.') self.error("Must be boy or girl.")
retString += random.choice(titleList) + ' '
# Put a space because there will surely be another name.
retString += random.choice(titleList) + " "
if firstFlag: if firstFlag:
# Shallow copy, since we will be altering the list
firstList = self.neutralFirsts[:] firstList = self.neutralFirsts[:]
if boy: if boy:
firstList += self.boyFirsts firstList += self.boyFirsts
elif girl: elif girl:
firstList += self.girlFirsts firstList += self.girlFirsts
else: else:
self.error('Must be boy or girl.') self.error("Must be boy or girl.")
retString += random.choice(firstList) retString += random.choice(firstList)
# Put a space if there is going to be a last name.
if lastFlag: if lastFlag:
retString += ' ' retString += " "
if lastFlag: if lastFlag:
lastPrefix = random.choice(self.lastPrefixes) lastPrefix = random.choice(self.lastPrefixes)
lastSuffix = random.choice(self.lastSuffixes) lastSuffix = random.choice(self.lastSuffixes)
if lastPrefix in self.capPrefixes: if lastPrefix in self.capPrefixes:
lastSuffix = lastSuffix.capitalize() lastSuffix = lastSuffix.capitalize()
retString += lastPrefix + lastSuffix retString += lastPrefix + lastSuffix
return retString return retString
def randomNameMoreinfo(self, boy = 0, girl = 0): def randomNameMoreinfo(self):
if boy and girl: """This is just like randomName only it returns a list where the first three
self.error("A name can't be both boy and girl!") values are titleFlag, firstFlag, and lastFlag and the next four values are
if not boy and not girl: the title, firstname, and lastname (if applicable, '' if not)
boy = random.choice([0, 1]) """
girl = not boy
uberFlag = random.choice(['title-first', # Five types of name combos
'title-last', uberFlag = random.choice(["title-first", "title-last",
'first', "first", "last", "first-last", "title-first-last"])
'last',
'first-last',
'title-first-last'])
titleFlag = 0 titleFlag = 0
if uberFlag == 'title-first' or uberFlag == 'title-last' or uberFlag == 'title-first-last': if ((uberFlag == "title-first") or
(uberFlag == "title-last") or
(uberFlag == "title-first-last")):
titleFlag = 1 titleFlag = 1
firstFlag = 0 firstFlag = 0
if uberFlag == 'title-first' or uberFlag == 'first' or uberFlag == 'first-last' or uberFlag == 'title-first-last': if ((uberFlag == "title-first") or
(uberFlag == "first") or
(uberFlag == "first-last") or
(uberFlag == "title-first-last")):
firstFlag = 1 firstFlag = 1
lastFlag = 0 lastFlag = 0
if uberFlag == 'title-last' or uberFlag == 'last' or uberFlag == 'first-last' or uberFlag == 'title-first-last': if ((uberFlag == "title-last") or
(uberFlag == "last") or
(uberFlag == "first-last") or
(uberFlag == "title-first-last")):
lastFlag = 1 lastFlag = 1
retString = ''
uberReturn = [0, retString = ""
0, uberReturn = [0, 0, 0, '', '', '', '']
0,
'',
'',
'',
'']
uberReturn[0] = titleFlag uberReturn[0] = titleFlag
uberReturn[1] = firstFlag uberReturn[1] = firstFlag
uberReturn[2] = lastFlag uberReturn[2] = lastFlag
# Choose random names in each slot even if we won't be using
# them. That way, if the user activates a previously deactive
# slot, s/he'll start at a random point in the list instead of
# always at the top.
# Shallow copy, since we will be altering the list
titleList = self.neutralTitles[:] titleList = self.neutralTitles[:]
if boy:
titleList += self.boyTitles titleList += self.boyTitles
elif girl:
titleList += self.girlTitles titleList += self.girlTitles
else:
self.error('Must be boy or girl.')
uberReturn[3] = random.choice(titleList) uberReturn[3] = random.choice(titleList)
# Shallow copy, since we will be altering the list
firstList = self.neutralFirsts[:] firstList = self.neutralFirsts[:]
if boy:
firstList += self.boyFirsts firstList += self.boyFirsts
elif girl:
firstList += self.girlFirsts firstList += self.girlFirsts
else:
self.error('Must be boy or girl.')
uberReturn[4] = random.choice(firstList) uberReturn[4] = random.choice(firstList)
lastPrefix = random.choice(self.lastPrefixes) lastPrefix = random.choice(self.lastPrefixes)
lastSuffix = random.choice(self.lastSuffixes) lastSuffix = random.choice(self.lastSuffixes)
if lastPrefix in self.capPrefixes: if lastPrefix in self.capPrefixes:
lastSuffix = lastSuffix.capitalize() lastSuffix = lastSuffix.capitalize()
uberReturn[5] = lastPrefix uberReturn[5] = lastPrefix
uberReturn[6] = lastSuffix uberReturn[6] = lastSuffix
# Put a space because there will surely be another name.
if titleFlag: if titleFlag:
retString += uberReturn[3] + ' ' retString += uberReturn[3] + " "
if firstFlag: if firstFlag:
retString += uberReturn[4] retString += uberReturn[4]
# Put a space if there is going to be a last name.
if lastFlag: if lastFlag:
retString += ' ' retString += " "
if lastFlag: if lastFlag:
retString += uberReturn[5] + uberReturn[6] retString += uberReturn[5] + uberReturn[6]
uberReturn.append(retString) uberReturn.append(retString)
return uberReturn return uberReturn
@ -309,16 +413,19 @@ class NameGenerator:
origBoy = boy origBoy = boy
origGirl = girl origGirl = girl
while i < total: while i < total:
if not origBoy and not origGirl: if (not origBoy) and (not origGirl):
# Randomly pick the name sex
boy = random.choice([0, 1]) boy = random.choice([0, 1])
girl = not boy girl = not boy
name = self.randomName(boy, girl) name = self.randomName(boy, girl)
width = self.text.calcWidth(name) width = self.text.calcWidth(name)
widthStr = str(width) widthStr = str(width)
if boy: if boy:
print('Boy: ' + name + ' (' + widthStr + ' units)') print("Boy: " + name + " (" + widthStr + " units)")
if girl: if girl:
print('Girl: ' + name + ' (' + widthStr + ' units)') print("Girl: " + name + " (" + widthStr + " units)")
i += 1 i += 1
def percentOver(self, limit=9.0, samples=1000): def percentOver(self, limit=9.0, samples=1000):
@ -330,34 +437,60 @@ class NameGenerator:
if width > limit: if width > limit:
over += 1 over += 1
i += 1 i += 1
percent = (float(over) / float(samples)) * 100
percent = float(over) / float(samples) * 100 print(("Samples: " + str(samples) + " Over: " +
print('Samples: ' + str(samples) + ' Over: ' + str(over) + ' Percent: ' + str(percent)) str(over) + " Percent: " + str(percent)))
def totalNames(self): def totalNames(self):
firsts = len(self.boyFirsts) + len(self.girlFirsts) + len(self.neutralFirsts) # Firsts only
print('Total firsts: ' + str(firsts)) firsts = (len(self.boyFirsts) + len(self.girlFirsts) +
len(self.neutralFirsts))
print("Total firsts: " + str(firsts))
# Lasts only
lasts = len(self.lastPrefixes) * len(self.lastSuffixes) lasts = len(self.lastPrefixes) * len(self.lastSuffixes)
print('Total lasts: ' + str(lasts)) print("Total lasts: " + str(lasts))
# Title plus first
neutralTitleFirsts = len(self.neutralTitles) * len(self.neutralFirsts) neutralTitleFirsts = len(self.neutralTitles) * len(self.neutralFirsts)
boyTitleFirsts = len(self.boyTitles) * (len(self.neutralFirsts) + len(self.boyFirsts)) + len(self.neutralTitles) * len(self.boyFirsts) boyTitleFirsts = ((len(self.boyTitles) *
girlTitleFirsts = len(self.girlTitles) * (len(self.neutralFirsts) + len(self.girlFirsts)) + len(self.neutralTitles) * len(self.girlFirsts) (len(self.neutralFirsts) + len(self.boyFirsts))) +
totalTitleFirsts = neutralTitleFirsts + boyTitleFirsts + girlTitleFirsts ((len(self.neutralTitles) *
print('Total title firsts: ' + str(totalTitleFirsts)) len(self.boyFirsts))))
girlTitleFirsts = ((len(self.girlTitles) *
(len(self.neutralFirsts) + len(self.girlFirsts))) +
((len(self.neutralTitles) *
len(self.girlFirsts))))
totalTitleFirsts = (neutralTitleFirsts + boyTitleFirsts +
girlTitleFirsts)
print("Total title firsts: " + str(totalTitleFirsts))
# Title plus last
neutralTitleLasts = len(self.neutralTitles) * lasts neutralTitleLasts = len(self.neutralTitles) * lasts
boyTitleLasts = len(self.boyTitles) * lasts boyTitleLasts = (len(self.boyTitles) *
girlTitleLasts = len(self.girlTitles) * lasts lasts)
totalTitleLasts = neutralTitleLasts + boyTitleFirsts + girlTitleLasts girlTitleLasts = (len(self.girlTitles) *
print('Total title lasts: ' + str(totalTitleLasts)) lasts)
totalTitleLasts = (neutralTitleLasts + boyTitleFirsts +
girlTitleLasts)
print("Total title lasts: " + str(totalTitleLasts))
# First plus last
neutralFirstLasts = len(self.neutralFirsts) * lasts neutralFirstLasts = len(self.neutralFirsts) * lasts
boyFirstLasts = len(self.boyFirsts) * lasts boyFirstLasts = len(self.boyFirsts) * lasts
girlFirstLasts = len(self.girlFirsts) * lasts girlFirstLasts = len(self.girlFirsts) * lasts
totalFirstLasts = neutralFirstLasts + boyFirstLasts + girlFirstLasts totalFirstLasts = (neutralFirstLasts + boyFirstLasts +
print('Total first lasts: ' + str(totalFirstLasts)) girlFirstLasts)
print("Total first lasts: " + str(totalFirstLasts))
# Title plus first plus last
neutralTitleFirstLasts = neutralTitleFirsts * lasts neutralTitleFirstLasts = neutralTitleFirsts * lasts
boyTitleFirstLasts = boyTitleFirsts * lasts boyTitleFirstLasts = boyTitleFirsts * lasts
girlTitleFirstLasts = girlTitleFirsts * lasts girlTitleFirstLasts = girlTitleFirsts * lasts
totalTitleFirstLasts = neutralTitleFirstLasts + boyTitleFirstLasts + girlTitleFirstLasts totalTitleFirstLasts = (neutralTitleFirstLasts + boyTitleFirstLasts + girlTitleFirstLasts)
print('Total title first lasts: ' + str(totalTitleFirstLasts)) print("Total title first lasts: " + str(totalTitleFirstLasts))
totalNames = firsts + lasts + totalTitleFirsts + totalTitleLasts + totalFirstLasts + totalTitleFirstLasts
print('Total Names: ' + str(totalNames)) # Total
totalNames = (firsts + lasts + totalTitleFirsts +
totalTitleLasts + totalFirstLasts + totalTitleFirstLasts)
print("Total Names: " + str(totalNames))

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ class ShuffleButton:
shuffleArrowDisabled = gui.find('**/tt_t_gui_mat_shuffleArrowDisabled') shuffleArrowDisabled = gui.find('**/tt_t_gui_mat_shuffleArrowDisabled')
gui.removeNode() gui.removeNode()
del gui del gui
self.parentFrame = DirectFrame(parent=self.parent.parentFrame, relief=DGG.RAISED, pos=(0, 0, -1), frameColor=(1, 0, 0, 0)) self.parentFrame = DirectFrame(parent=self.parent.parentFrame, relief=DGG.RAISED, pos=(0, 0, -1.1), frameColor=(1, 0, 0, 0))
self.shuffleFrame = DirectFrame(parent=self.parentFrame, image=shuffleFrame, image_scale=halfButtonInvertScale, relief=None, frameColor=(1, 1, 1, 1)) self.shuffleFrame = DirectFrame(parent=self.parentFrame, image=shuffleFrame, image_scale=halfButtonInvertScale, relief=None, frameColor=(1, 1, 1, 1))
self.shuffleFrame.hide() self.shuffleFrame.hide()
self.shuffleBtn = DirectButton(parent=self.parentFrame, relief=None, image=(shuffleUp, shuffleDown, shuffleUp), image_scale=halfButtonInvertScale, image1_scale=(-0.63, 0.6, 0.6), image2_scale=(-0.63, 0.6, 0.6), text=(TTLocalizer.ShuffleButton, self.shuffleBtn = DirectButton(parent=self.parentFrame, relief=None, image=(shuffleUp, shuffleDown, shuffleUp), image_scale=halfButtonInvertScale, image1_scale=(-0.63, 0.6, 0.6), image2_scale=(-0.63, 0.6, 0.6), text=(TTLocalizer.ShuffleButton,

View file

@ -7,13 +7,12 @@ class TTPickANamePattern(PickANamePatternTwoPartLastName):
NameParts = None NameParts = None
LastNamePrefixesCapped = None LastNamePrefixesCapped = None
def _getNameParts(self, gender): def _getNameParts(self):
if TTPickANamePattern.NameParts is None: if TTPickANamePattern.NameParts is None:
TTPickANamePattern.NameParts = {} TTPickANamePattern.NameParts = {}
ng = NameGenerator() ng = NameGenerator()
TTPickANamePattern.NameParts['m'] = ng.getMaleNameParts() TTPickANamePattern.NameParts['n'] = ng.getAllNameParts()
TTPickANamePattern.NameParts['f'] = ng.getFemaleNameParts() return TTPickANamePattern.NameParts['n']
return TTPickANamePattern.NameParts[gender]
def _getLastNameCapPrefixes(self): def _getLastNameCapPrefixes(self):
if TTPickANamePattern.LastNamePrefixesCapped is None: if TTPickANamePattern.LastNamePrefixesCapped is None:

View file

@ -737,20 +737,14 @@ class DistributedPhotoGame(DistributedMinigame, PhotoGameBase.PhotoGameBase):
for pathIndex in range(len(self.data['PATHS'])): for pathIndex in range(len(self.data['PATHS'])):
path = self.data['PATHS'][pathIndex] path = self.data['PATHS'][pathIndex]
subject = Toon.Toon() subject = Toon.Toon()
gender = random.choice(['m', 'f']) eyelashes = random.choice([0, 1])
seed = int(random.random() * 571) seed = int(random.random() * 571)
if gender == 'm': subject.setName(namegen.randomNameMoreinfo()[-1])
boy = 1
girl = 0
else:
boy = 0
girl = 1
subject.setName(namegen.randomNameMoreinfo(boy=boy, girl=girl)[-1])
self.nameCounter += 1 self.nameCounter += 1
subject.setPickable(0) subject.setPickable(0)
subject.setPlayerType(NametagGroup.CCNonPlayer) subject.setPlayerType(NametagGroup.CCNonPlayer)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToonRandom(seed, gender, 1) dna.newToonRandom(seed, eyelashes, 1)
subject.setDNAString(dna.makeNetString()) subject.setDNAString(dna.makeNetString())
subject.animFSM.request('neutral') subject.animFSM.request('neutral')
subject.setTag('subjectIndex', '%s' % len(self.subjects)) subject.setTag('subjectIndex', '%s' % len(self.subjects))

View file

@ -488,10 +488,7 @@ class NPCMoviePlayer(DirectObject.DirectObject):
def parseLoadCCDialogue(self, line): def parseLoadCCDialogue(self, line):
token, varName, filenameTemplate = line token, varName, filenameTemplate = line
if self.toon.getStyle().gender == 'm':
classicChar = 'mickey' classicChar = 'mickey'
else:
classicChar = 'minnie'
filename = filenameTemplate % classicChar filename = filenameTemplate % classicChar
if base.config.GetString('language', 'english') == 'japanese': if base.config.GetString('language', 'english') == 'japanese':
dialogue = base.loader.loadSfx(filename) dialogue = base.loader.loadSfx(filename)
@ -517,10 +514,7 @@ class NPCMoviePlayer(DirectObject.DirectObject):
token, name = line token, name = line
char = Char.Char() char = Char.Char()
dna = CharDNA.CharDNA() dna = CharDNA.CharDNA()
if self.toon.getStyle().gender == 'm':
charType = 'mk' charType = 'mk'
else:
charType = 'mn'
dna.newChar(charType) dna.newChar(charType)
char.setDNA(dna) char.setDNA(dna)
char.startEarTask() char.startEarTask()
@ -720,10 +714,7 @@ class NPCMoviePlayer(DirectObject.DirectObject):
lineLength = len(line) lineLength = len(line)
avatarName = line[1] avatarName = line[1]
avatar = self.getVar(avatarName) avatar = self.getVar(avatarName)
if self.toon.getStyle().gender == 'm':
chatString = eval('TTLocalizer.' + line[2] % 'Mickey') chatString = eval('TTLocalizer.' + line[2] % 'Mickey')
else:
chatString = eval('TTLocalizer.' + line[2] % 'Minnie')
quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[3:]) quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[3:])
return Func(avatar.setLocalPageChat, chatString, quitButton, extraChatFlags, dialogueList) return Func(avatar.setLocalPageChat, chatString, quitButton, extraChatFlags, dialogueList)
@ -735,10 +726,7 @@ class NPCMoviePlayer(DirectObject.DirectObject):
toAvatar = self.getVar(toAvatarKey) toAvatar = self.getVar(toAvatarKey)
localizerAvatarName = toAvatar.getName().capitalize() localizerAvatarName = toAvatar.getName().capitalize()
toAvatarName = eval('TTLocalizer.' + localizerAvatarName) toAvatarName = eval('TTLocalizer.' + localizerAvatarName)
if self.toon.getStyle().gender == 'm':
chatString = eval('TTLocalizer.' + line[3] % 'Mickey') chatString = eval('TTLocalizer.' + line[3] % 'Mickey')
else:
chatString = eval('TTLocalizer.' + line[3] % 'Minnie')
chatString = chatString.replace('%s', toAvatarName) chatString = chatString.replace('%s', toAvatarName)
quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[4:]) quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[4:])
return Func(avatar.setLocalPageChat, chatString, quitButton, extraChatFlags, dialogueList) return Func(avatar.setLocalPageChat, chatString, quitButton, extraChatFlags, dialogueList)

View file

@ -131,9 +131,9 @@ class QuestPoster(DirectFrame):
def createNpcToonHead(self, toNpcId): def createNpcToonHead(self, toNpcId):
npcInfo = NPCToons.NPCToonDict[toNpcId] npcInfo = NPCToons.NPCToonDict[toNpcId]
dnaList = npcInfo[2] dnaList = npcInfo[2]
gender = npcInfo[3] eyelashes = npcInfo[3]
if dnaList == 'r': if dnaList == 'r':
dnaList = NPCToons.getRandomDNA(toNpcId, gender) dnaList = NPCToons.getRandomDNA(toNpcId, eyelashes)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToonFromProperties(*dnaList) dna.newToonFromProperties(*dnaList)
head = ToonHead.ToonHead() head = ToonHead.ToonHead()

View file

@ -1,6 +1,5 @@
GiveAwardErrors = Enum('Success, WrongGender, NotGiftable, FullMailbox, FullAwardMailbox, AlreadyInMailbox, AlreadyInGiftQueue, AlreadyInOrderedQueue, AlreadyInCloset, AlreadyBeingWorn, AlreadyInAwardMailbox, AlreadyInThirtyMinuteQueue, AlreadyInMyPhrases, AlreadyKnowDoodleTraining, AlreadyRented, GenericAlreadyHaveError, UnknownError, UnknownToon, NonToon,') GiveAwardErrors = Enum('Success, NotGiftable, FullMailbox, FullAwardMailbox, AlreadyInMailbox, AlreadyInGiftQueue, AlreadyInOrderedQueue, AlreadyInCloset, AlreadyBeingWorn, AlreadyInAwardMailbox, AlreadyInThirtyMinuteQueue, AlreadyInMyPhrases, AlreadyKnowDoodleTraining, AlreadyRented, GenericAlreadyHaveError, UnknownError, UnknownToon, NonToon,')
GiveAwardErrorStrings = {GiveAwardErrors.Success: 'success', GiveAwardErrorStrings = {GiveAwardErrors.Success: 'success',
GiveAwardErrors.WrongGender: 'wrong gender',
GiveAwardErrors.NotGiftable: 'item is not giftable', GiveAwardErrors.NotGiftable: 'item is not giftable',
GiveAwardErrors.FullMailbox: 'mailbox is full', GiveAwardErrors.FullMailbox: 'mailbox is full',
GiveAwardErrors.FullAwardMailbox: 'award mailbox is full', GiveAwardErrors.FullAwardMailbox: 'award mailbox is full',

View file

@ -0,0 +1,60 @@
######################################################################
# This file is meant for unit testing the ScavengerHunt system. #
# It can also be used to demonstrate how the system should be #
# used. #
# #
# It's meant to be run after any changes are made to the system. #
# #
# Usage: python SHtest.py #
######################################################################
from .ScavengerHuntBase import ScavengerHuntBase
import unittest,copy
hunt = ScavengerHuntBase(scavengerHuntId = 12,scavengerHuntType = 3)
hunt.defineGoals(list(range(1,6)))
hunt.defineMilestones([[0,list(range(1,4))],[1,list(range(1,6))]])
class MilestoneTestCase(unittest.TestCase):
def testDefineGoals(self):
gc = set(range(1,6))
self.assertEqual(hunt.goals,gc)
def testDefineMilestones(self):
m = {}
gc = list(range(1,4))
m[frozenset(gc)] = 0
gc = list(range(1,6))
m[frozenset(gc)] = 1
self.assertEqual(hunt.milestones,m)
def testRecentMilestonesHit(self):
gc = list(range(1,4))
m = hunt.getRecentMilestonesHit(gc,2)
self.assertEqual([0],m)
gc = list(range(1,6))
m = hunt.getRecentMilestonesHit(gc,2)
m.sort()
self.assertEqual([0,1],m)
def testRecentMilestonesMissed(self):
gc = list(range(1,5))
m = hunt.getRecentMilestonesHit(gc,4)
self.assertEqual([],m)
def testAllMilestonesHit(self):
gc = list(range(1,6))
m = hunt.getAllMilestonesHit(gc)
m.sort()
M = list(hunt.milestones.values())
M.sort()
self.assertEqual(M,m)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,65 @@
#from direct.directnotify import DirectNotifyGlobal
class ScavengerHuntBase:
"""
Base class for all hunts. There is enough functionality here
such that you shouldn't need to subclass it, though.
"""
def __init__(self,scavengerHuntId, scavengerHuntType):
self.id = scavengerHuntId
self.type = scavengerHuntType
self.goals = set()
self.milestones = {}
def defineGoals(self,goalIds):
"""
Accepts a list of Goal identifiers. This could be something as
simple as a range of integers corresponding to the goals in the
hunt.
"""
self.goals = set(goalIds)
def defineMilestones(self,milestones = []):
"""
Accepts a list with items of the format:
[milestoneId,[goal1,goal2,goal3,...]]
"""
for id,stone in milestones:
self.milestones[frozenset(stone)] = id
def getRecentMilestonesHit(self,goals,mostRecentGoal):
"""
Given a list of goals, and the most recent goal added to that
list, return a list of milestone ids which that latest goal would
trigger.
"""
milestones = []
for milestone in list(self.milestones.keys()):
if mostRecentGoal in milestone and milestone.issubset(goals):
milestones.append(self.milestones[milestone])
return milestones
def getAllMilestonesHit(self,goals):
"""
Return a list of milestone ids which are satisfied by the Goals listed
in goals.
"""
milestones = []
for milestone in list(self.milestones.keys()):
if(milestone.issubset(goals)):
milestones.append(self.milestones[milestone])
return milestones

View file

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

View file

View file

@ -176,7 +176,6 @@ class MagicWord(DirectObject):
def handleWord(self, invoker, avId, toon, *args): def handleWord(self, invoker, avId, toon, *args):
raise NotImplementedError raise NotImplementedError
class SetHP(MagicWord): class SetHP(MagicWord):
aliases = ["hp", "setlaff", "laff"] aliases = ["hp", "setlaff", "laff"]
desc = "Sets the target's current laff." desc = "Sets the target's current laff."
@ -200,7 +199,6 @@ class SetHP(MagicWord):
toon.b_setHp(hp) toon.b_setHp(hp)
return "{}'s laff has been set to {}.".format(toon.getName(), hp) return "{}'s laff has been set to {}.".format(toon.getName(), hp)
class SetMaxHP(MagicWord): class SetMaxHP(MagicWord):
aliases = ["maxhp", "setmaxlaff", "maxlaff"] aliases = ["maxhp", "setmaxlaff", "maxlaff"]
desc = "Sets the target's max laff." desc = "Sets the target's max laff."
@ -219,7 +217,6 @@ class SetMaxHP(MagicWord):
toon.toonUp(maxhp) toon.toonUp(maxhp)
return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp) return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp)
class ToggleOobe(MagicWord): class ToggleOobe(MagicWord):
aliases = ["oobe"] aliases = ["oobe"]
desc = "Toggles the out of body experience mode, which lets you move the camera freely." desc = "Toggles the out of body experience mode, which lets you move the camera freely."
@ -232,7 +229,6 @@ class ToggleOobe(MagicWord):
base.oobe() base.oobe()
return "Oobe mode has been toggled." return "Oobe mode has been toggled."
class ToggleRun(MagicWord): class ToggleRun(MagicWord):
aliases = ["run"] aliases = ["run"]
desc = "Toggles run mode, which gives you a faster running speed." desc = "Toggles run mode, which gives you a faster running speed."
@ -309,6 +305,16 @@ class Inventory(MagicWord):
return ("Zeroing inventory for " + toon.getName() + ".") return ("Zeroing inventory for " + toon.getName() + ".")
class ToggleFps(MagicWord):
# this command toggles the fps meter
aliases = ["fps"]
desc = "Toggles the FPS meter."
execLocation = MagicWordConfig.EXEC_LOC_CLIENT
def handleWord(self, invoker, avId, toon, *args):
base.setFrameRateMeter(not base.frameRateMeter)
return f"Toggled FPS meter.: {not base.frameRateMeter}"
class SetPinkSlips(MagicWord): class SetPinkSlips(MagicWord):
# this command gives the target toon the specified amount of pink slips # this command gives the target toon the specified amount of pink slips
@ -332,6 +338,51 @@ class AbortMinigame(MagicWord):
messenger.send("minigameAbort") messenger.send("minigameAbort")
return "Requested minigame abort." return "Requested minigame abort."
class SkipMiniGolfHole(MagicWord):
aliases = ["skipgolfhole", "skipgolf", "skiphole"]
desc = "Skips the current golf hole."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = []
def handleWord(self, invoker, avId, toon, *args):
from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI
course = None
for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it.
if isinstance(do, DistributedGolfCourseAI):
if invoker.doId in do.avIdList:
course = do
break
if not course:
return "You aren't in a golf course!"
if course.isPlayingLastHole(): # If the Toon is on the final hole, calling holeOver() will softlock, so instead we move onto the reward screen.
course.demand('WaitReward')
else:
course.holeOver()
return "Skipped the current hole."
class AbortGolfCourse(MagicWord):
aliases = ["abortminigolf", "abortgolf", "abortcourse", "leavegolf", "leavecourse"]
desc = "Aborts the current golf course."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = []
def handleWord(self, invoker, avId, toon, *args):
from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI
course = None
for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it.
if isinstance(do, DistributedGolfCourseAI):
if invoker.doId in do.avIdList:
course = do
break
if not course:
return "You aren't in a golf course!"
course.setCourseAbort()
return "Aborted golf course."
class Minigame(MagicWord): class Minigame(MagicWord):
aliases = ["mg"] aliases = ["mg"]
desc = "Teleport to or request the next trolley minigame." desc = "Teleport to or request the next trolley minigame."
@ -516,7 +567,7 @@ class BossBattle(MagicWord):
if not start: if not start:
respText += " in Frolic state" respText += " in Frolic state"
return respText + ", teleporting...", ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0] return respText + ", teleporting...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0]
elif command == "list": elif command == "list":
# List all the ongoing boss battles. # List all the ongoing boss battles.
@ -558,7 +609,7 @@ class BossBattle(MagicWord):
return "Index out of range!" return "Index out of range!"
boss = AllBossCogs[index] boss = AllBossCogs[index]
return "Teleporting to boss battle...", ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0] return "Teleporting to boss battle...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0]
# The following commands needs the invoker to be in a boss battle. # The following commands needs the invoker to be in a boss battle.
@ -597,13 +648,129 @@ class BossBattle(MagicWord):
# The create command is already described when the invoker is not in a battle. These are the commands # The create command is already described when the invoker is not in a battle. These are the commands
# they can use INSIDE the battle. # they can use INSIDE the battle.
return respText + f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"." return f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"."
def __destroyBoss(self, boss): def __destroyBoss(self, boss):
bossZone = boss.zoneId bossZone = boss.zoneId
boss.requestDelete() boss.requestDelete()
self.air.deallocateZone(bossZone) self.air.deallocateZone(bossZone)
class GlobalTeleport(MagicWord):
aliases = ["globaltp", "tpaccess"]
desc = "Enables teleport access to all zones."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
def handleWord(self, invoker, avId, toon, *args):
from toontown.toonbase import ToontownGlobals
toon.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll)
toon.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll)
return f"Enabled teleport access to all zones for {toon.getName()}."
class Teleport(MagicWord):
aliases = ["tp", "goto"]
desc = "Teleport to a specified zone."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = [("zoneName", str, False, '')]
def handleWord(self, invoker, avId, toon, *args):
from toontown.hood import ZoneUtil
from toontown.toonbase import ToontownGlobals
zoneName = args[0]
# Can add stuff like streets to this too if you wanted, but if you do you'll want it to be a valid zone on that street. eg: 2100 is invalid, but any value 2101 to 2156 is fine.
# so if you wanted to add a silly street key, theroetically you could do something like this: 'sillystreet': ToontownGlobals.SillyStreet +1,
zoneName2Id = {'ttc': ToontownGlobals.ToontownCentral,
'dd': ToontownGlobals.DonaldsDock,
'dg': ToontownGlobals.DaisyGardens,
'mml': ToontownGlobals.MinniesMelodyland,
'tb': ToontownGlobals.TheBrrrgh,
'ddl': ToontownGlobals.DonaldsDreamland,
'gs': ToontownGlobals.GoofySpeedway,
'oz': ToontownGlobals.OutdoorZone,
'aa': ToontownGlobals.OutdoorZone,
'gz': ToontownGlobals.GolfZone,
'sbhq': ToontownGlobals.SellbotHQ,
'factory': ToontownGlobals.SellbotFactoryExt,
'cbhq': ToontownGlobals.CashbotHQ,
'lbhq': ToontownGlobals.LawbotHQ,
'bbhq': ToontownGlobals.BossbotHQ}
try:
zone = zoneName2Id[zoneName]
except KeyError:
return "Unknown zone name!"
return f"Requested to teleport {toon.getName()} to zone {zone}.", toon.doId, [ZoneUtil.getBranchLoaderName(zone), ZoneUtil.getToonWhereName(zone), "", ZoneUtil.getHoodId(zone), zone, 0]
class ToggleSleep(MagicWord):
aliases = ["sleep", "nosleep", "neversleep", "togglesleeping", "insomnia"]
desc = "Toggles sleeping for the target."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
def handleWord(self, invoker, avId, toon, *args):
toon.d_toggleSleep()
return f"Toggled sleeping for {toon.getName()}."
class ToggleImmortal(MagicWord):
aliases = ["immortal", "invincible", "invulnerable"]
desc = "Toggle immortal mode. This makes the Toon immune to damage."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
def handleWord(self, invoker, avId, toon, *args):
toon.setImmortalMode(not toon.immortalMode)
return f"Toggled immortal mode for {toon.getName()}"
class ToggleGhost(MagicWord):
aliases = ["ghost", "invisible", "spy"]
desc = "Toggle ghost mode."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
def handleWord(self, invoker, avId, toon, *args):
# 1 is for the attic, 2 enables you to see yourself other ghost toons. 0 is off.
toon.b_setGhostMode(2 if not toon.ghostMode else 0) # As it's primarily for moderation purposes, we set it to 2 here, or 0 if it's already on.
return f"Toggled ghost mode for {toon.getName()}"
class SetGM(MagicWord):
aliases = ["icon", "seticon", "gm", "gmicon", "setgmicon"]
desc = "Sets the GM icon on the target."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = [("iconRequest", int, False, 0),]
def handleWord(self, invoker, avId, toon, *args):
from toontown.toonbase import TTLocalizer
iconRequest = args[0]
if iconRequest > len(TTLocalizer.GM_NAMES) or iconRequest < 0:
return "Invalid GM icon ID!"
toon.b_setGM(0) # Reset it first, otherwise the Toon keeps the old icon, but the name still changes.
toon.b_setGM(iconRequest)
return f"GM icon set to {iconRequest} for {toon.getName()}"
class SetMaxCarry(MagicWord):
aliases = ["gagpouch", "pouch", "gagcapacity"]
desc = "Set a Toon's gag pouch size."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = [("pouchSize", int, True)]
def handleWord(self, invoker, avId, toon, *args):
pouchSize = args[0]
if pouchSize > 255 or pouchSize < 0:
return "Specified pouch size must be between 1 and 255."
toon.b_setMaxCarry(pouchSize)
return f"Set gag pouch size to {pouchSize} for {toon.getName()}"
class ToggleInstantKill(MagicWord):
aliases = ["instantkill", "instakill"]
desc = "Toggle the ability to instantly kill a Cog with any gag."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
def handleWord(self, invoker, avId, toon, *args):
toon.setInstantKillMode(not toon.instantKillMode)
return f"Toggled instant-kill mode for {toon.getName()}"
class Fireworks(MagicWord): class Fireworks(MagicWord):
aliases = ["firework"] aliases = ["firework"]
desc = "Starts a firework show." desc = "Starts a firework show."

View file

@ -195,7 +195,7 @@ class DistributedBossbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM):
npc.setPickable(0) npc.setPickable(0)
npc.setPlayerType(NametagGroup.CCNonPlayer) npc.setPlayerType(NametagGroup.CCNonPlayer)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToonRandom(335933, 'm', 1) dna.newToonRandom(335933, 1, 1)
dna.head = 'sls' dna.head = 'sls'
npc.setDNAString(dna.makeNetString()) npc.setDNAString(dna.makeNetString())
npc.animFSM.request('neutral') npc.animFSM.request('neutral')

View file

@ -103,7 +103,7 @@ class DistributedCashbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM):
npc.setPickable(0) npc.setPickable(0)
npc.setPlayerType(NametagGroup.CCNonPlayer) npc.setPlayerType(NametagGroup.CCNonPlayer)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToonRandom(146392, 'f', 1) dna.newToonRandom(146392, 0, 1)
dna.head = 'pls' dna.head = 'pls'
npc.setDNAString(dna.makeNetString()) npc.setDNAString(dna.makeNetString())
npc.animFSM.request('neutral') npc.animFSM.request('neutral')

View file

@ -0,0 +1,285 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from . import SuitInvasionManagerAI
from toontown.toonbase import ToontownGlobals
class HolidaySuitInvasionManagerAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory('HolidaySuitInvasionManagerAI')
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
# Stop any current invasion that might be happening by chance
if self.air.suitInvasionManager.getInvading():
self.notify.info("Stopping current invasion to make room for holiday %s" %
(self.holidayId))
self.air.suitInvasionManager.stopInvasion()
if not simbase.config.GetBool('want-invasions', 1):
return 1
if (self.holidayId == ToontownGlobals.HALLOWEEN):
# Bloodsucker invasion on Halloween
cogType = 'b'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.SKELECOG_INVASION):
# any cog will do
from . import SuitDNA
import random
cogType = random.choice(SuitDNA.suitHeadTypes)
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 1
elif (self.holidayId == ToontownGlobals.MR_HOLLYWOOD_INVASION):
# Mr. Hollywood of course...
cogType = 'mh'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.BOSSCOG_INVASION):
# any cog will do
from . import SuitDNA
import random
cogType = SuitDNA.getRandomSuitByDept('c')
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.MARCH_INVASION):
# Backstabbers...
cogType = 'bs'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.DECEMBER_INVASION):
# Sellbots...
cogType = 'cc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_1):
# Sellbot Surprise... cold caller
cogType = 'cc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_2 or \
self.holidayId == ToontownGlobals.NAME_DROPPER_INVASION):
# Sellbot Surprise ... Name dropper
cogType = 'nd'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_3):
# Sellbot Surprise ... gladhander
cogType = 'gh'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.SELLBOT_SURPRISE_4 or \
self.holidayId == ToontownGlobals.MOVER_AND_SHAKER_INVASION):
# Sellbot Surprise ... mover & shaker
cogType = 'ms'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_1):
# Cashbot Conundrum... Short Change
cogType = 'sc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_2 or \
self.holidayId == ToontownGlobals.PENNY_PINCHER_INVASION):
# Cashbot Conundrum... Penny Pincher
cogType = 'pp'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_3):
# Cashbot Conundrum... Bean Counter
cogType = 'bc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.CASHBOT_CONUNDRUM_4 or \
self.holidayId == ToontownGlobals.NUMBER_CRUNCHER_INVASION):
# Cashbot Conundrum... Number Cruncher
cogType = 'nc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_1):
# Lawbot Gambit... bottom feeder
cogType = 'bf'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_2 or \
self.holidayId == ToontownGlobals.DOUBLE_TALKER_INVASION):
# Lawbot Gambit... double talker
cogType = 'dt'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_3 or \
self.holidayId == ToontownGlobals.AMBULANCE_CHASER_INVASION):
# Lawbot Gambit... ambulance chaser
cogType = 'ac'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.LAWBOT_GAMBIT_4):
# Lawbot Gambit... back stabber
cogType = 'bs'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_1):
# The Trouble with Bossbots ... flunky
cogType = 'f'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_2):
# The Trouble with Bossbots ... pencil pusher
cogType = 'p'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_3 or \
self.holidayId == ToontownGlobals.MICROMANAGER_INVASION):
# The Trouble with Bossbots ... micro manager
cogType = 'mm'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TROUBLE_BOSSBOTS_4 or \
self.holidayId == ToontownGlobals.DOWN_SIZER_INVASION ):
# The Trouble with Bossbots ... downsizer
cogType = 'ds'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.COLD_CALLER_INVASION):
cogType = 'cc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.BEAN_COUNTER_INVASION):
cogType = 'bc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.DOUBLE_TALKER_INVASION):
# The Trouble with Bossbots ... downsizer
cogType = 'dt'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.DOWNSIZER_INVASION):
# The Trouble with Bossbots ... downsizer
cogType = 'ds'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.YES_MAN_INVASION):
# The Trouble with Bossbots ... yes man
cogType = 'ym'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TIGHTWAD_INVASION):
# tightwad
cogType = 'tw'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TELEMARKETER_INVASION):
# telemarketer
cogType = 'tm'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.HEADHUNTER_INVASION):
# head hunter
cogType = 'hh'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.SPINDOCTOR_INVASION):
# spin doctor
cogType = 'sd'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.MONEYBAGS_INVASION):
# money bags
cogType = 'mb'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.TWOFACES_INVASION):
# two faces
cogType = 'tf'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.MINGLER_INVASION):
# mingler
cogType = 'm'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.LOANSHARK_INVASION):
# loan sharks
cogType = 'ls'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.CORPORATE_RAIDER_INVASION):
# corporate raider
cogType = 'cr'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.LEGAL_EAGLE_INVASION):
# legal eagle
cogType = 'le'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.ROBBER_BARON_INVASION):
# robber baron
cogType = 'rb'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.BIG_WIG_INVASION):
# big wig
cogType = 'bw'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
elif (self.holidayId == ToontownGlobals.BIG_CHEESE_INVASION):
# big cheese
cogType = 'tbc'
# Max the number so they will not run out
numCogs = 1000000000
skeleton = 0
else:
self.notify.warning("Unrecognized holidayId: %s" % (self.holidayId))
return 0
self.air.suitInvasionManager.startInvasion(cogType, numCogs, skeleton)
self.air.suitInvasionManager.waitForNextInvasion()
return 1
def stop(self):
# Holiday is over, stop the invasion if it happens to still be running
if self.air.suitInvasionManager.getInvading():
self.notify.info("Prematurely stopping holiday invasion: %s" % (self.holidayId))
self.air.suitInvasionManager.stopInvasion()

View file

@ -1,13 +1,151 @@
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.battle import SuitBattleGlobals
import random
from direct.task import Task
class SuitInvasionManagerAI: class SuitInvasionManagerAI:
"""
Manages invasions of Suits
"""
notify = DirectNotifyGlobal.directNotify.newCategory('SuitInvasionManagerAI') notify = DirectNotifyGlobal.directNotify.newCategory('SuitInvasionManagerAI')
def __init__(self, air): def __init__(self, air):
self.air = air self.air = air
self.invading = 0
self.cogType = None
self.skeleton = 0
self.totalNumCogs = 0
self.numCogsRemaining = 0
def getInvadingCog(self): # Set of cog types to choose from See
return None, 0 # 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): def getInvading(self):
return False return self.invading
def getCogType(self):
return self.cogType, self.isSkeleton
def getNumCogsRemaining(self):
return self.numCogsRemaining
def getTotalNumCogs(self):
return self.totalNumCogs
def startInvasion(self, cogType, totalNumCogs, skeleton=0):
if self.invading:
self.notify.warning("startInvasion: already invading cogType: %s numCogsRemaining: %s" %
(cogType, self.numCogsRemaining))
return 0
if not SuitBattleGlobals.SuitAttributes.get(cogType):
self.notify.warning("startInvasion: unknown cogType: %s" % cogType)
return 0
self.notify.info("startInvasion: cogType: %s totalNumCogs: %s skeleton: %s" %
(cogType, totalNumCogs, skeleton))
self.invading = 1
self.cogType = cogType
self.isSkeleton = skeleton
self.totalNumCogs = totalNumCogs
self.numCogsRemaining = self.totalNumCogs
# Tell the news manager that an invasion is beginning
self.air.newsManager.invasionBegin(self.cogType, self.totalNumCogs, self.isSkeleton)
# Get rid of all the current cogs on the streets
# (except those already in battle, they can stay)
for suitPlanner in list(self.air.suitPlanners.values()):
suitPlanner.flySuits()
# Success!
return 1
def getInvadingCog(self):
if self.invading:
self.numCogsRemaining -= 1
if self.numCogsRemaining <= 0:
self.stopInvasion()
self.notify.debug("getInvadingCog: returned cog: %s, num remaining: %s" %
(self.cogType, self.numCogsRemaining))
return self.cogType, self.isSkeleton
else:
self.notify.debug("getInvadingCog: not currently invading")
return None, None
def stopInvasion(self):
self.notify.info("stopInvasion: invasion is over now")
# Tell the news manager that an invasion is ending
self.air.newsManager.invasionEnd(self.cogType, 0, self.isSkeleton)
self.invading = 0
self.cogType = None
self.isSkeleton = 0
self.totalNumCogs = 0
self.numCogsRemaining = 0
# Get rid of all the current invasion cogs on the streets
# (except those already in battle, they can stay)
for suitPlanner in list(self.air.suitPlanners.values()):
suitPlanner.flySuits()
# Need this here since this is not a distributed object
def taskName(self, taskString):
return (taskString + "-" + str(hash(self)))

View file

@ -143,7 +143,7 @@ class DistributedNPCTailor(DistributedNPCToonBase):
if self.isLocalToon: if self.isLocalToon:
taskMgr.doMethodLater(3.0, self.popupPurchaseGUI, self.uniqueName('popupPurchaseGUI')) taskMgr.doMethodLater(3.0, self.popupPurchaseGUI, self.uniqueName('popupPurchaseGUI'))
print('-----------Starting tailor interaction-----------') print('-----------Starting tailor interaction-----------')
print('avid: %s, gender: %s' % (self.av.doId, self.av.style.gender)) print('avid: %s' % (self.av.doId))
print('current top = %s,%s,%s,%s and bot = %s,%s,' % (self.av.style.topTex, print('current top = %s,%s,%s,%s and bot = %s,%s,' % (self.av.style.topTex,
self.av.style.topTexColor, self.av.style.topTexColor,
self.av.style.sleeveTex, self.av.style.sleeveTex,
@ -157,7 +157,7 @@ class DistributedNPCTailor(DistributedNPCToonBase):
self.setChatAbsolute(TTLocalizer.STOREOWNER_GOODBYE, CFSpeech | CFTimeout) self.setChatAbsolute(TTLocalizer.STOREOWNER_GOODBYE, CFSpeech | CFTimeout)
if self.av and self.isLocalToon: if self.av and self.isLocalToon:
print('-----------ending tailor interaction-----------') print('-----------ending tailor interaction-----------')
print('avid: %s, gender: %s' % (self.av.doId, self.av.style.gender)) print('avid: %s' % (self.av.doId))
print('current top = %s,%s,%s,%s and bot = %s,%s,' % (self.av.style.topTex, print('current top = %s,%s,%s,%s and bot = %s,%s,' % (self.av.style.topTex,
self.av.style.topTexColor, self.av.style.topTexColor,
self.av.style.sleeveTex, self.av.style.sleeveTex,

View file

@ -489,10 +489,8 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo
changed = False changed = False
if self.isPlayerControlled(): if self.isPlayerControlled():
allowedColors = [] allowedColors = []
if self.dna.gender == 'm': allowedColors = ToonDNA.defaultColorList + [26]
allowedColors = ToonDNA.defaultBoyColorList + [26]
else:
allowedColors = ToonDNA.defaultGirlColorList + [26]
if self.dna.legColor not in allowedColors: if self.dna.legColor not in allowedColors:
self.dna.legColor = allowedColors[0] self.dna.legColor = allowedColors[0]
changed = True changed = True

View file

@ -208,11 +208,15 @@ class NPCFriendCard(DirectFrame):
def createNPCToonHead(self, NPCID, dimension = 0.5): def createNPCToonHead(self, NPCID, dimension = 0.5):
NPCInfo = NPCToons.NPCToonDict[NPCID] NPCInfo = NPCToons.NPCToonDict[NPCID]
dnaList = NPCInfo[2] dnaList = NPCInfo[2]
gender = NPCInfo[3] eyelashes = NPCInfo[3]
if eyelashes == 'm':
eyelashes = 0
elif eyelashes == 'f':
eyelashes = 1
if dnaList == 'r': if dnaList == 'r':
dnaList = NPCToons.getRandomDNA(NPCID, gender) dnaList = NPCToons.getRandomDNA(NPCID, eyelashes)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
dna.newToonFromProperties(*dnaList) dna.newToonFromProperties(*dnaList, isNPC=True)
head = ToonHead.ToonHead() head = ToonHead.ToonHead()
head.setupHead(dna, forGui=1) head.setupHead(dna, forGui=1)
self.fitGeometry(head, fFlip=1, dimension=dimension) self.fitGeometry(head, fFlip=1, dimension=dimension)

View file

@ -63,9 +63,10 @@ TAILOR_COUNTDOWN_TIME = 300
RTDNAFile = '/RTDNAFile.txt' RTDNAFile = '/RTDNAFile.txt'
saveDNA = False saveDNA = False
def getRandomDNA(seed, gender):
def getRandomDNA(seed, eyelashes):
randomDNA = ToonDNA.ToonDNA() randomDNA = ToonDNA.ToonDNA()
randomDNA.newToonRandom(seed, gender, 1) randomDNA.newToonRandom(seed, eyelashes, 1)
return randomDNA.asTuple() return randomDNA.asTuple()
@ -81,7 +82,7 @@ def createNPC(air, npcId, desc, zoneId, posIndex = 0, questCallback = None):
from . import DistributedNPCSpecialQuestGiverAI from . import DistributedNPCSpecialQuestGiverAI
from . import DistributedNPCFlippyInToonHallAI from . import DistributedNPCFlippyInToonHallAI
from . import DistributedNPCScientistAI from . import DistributedNPCScientistAI
canonicalZoneId, name, dnaType, gender, protected, type = desc canonicalZoneId, name, dnaType, eyelashes, protected, type = desc
if type == NPC_REGULAR: if type == NPC_REGULAR:
npc = DistributedNPCToonAI.DistributedNPCToonAI(air, npcId, questCallback=questCallback) npc = DistributedNPCToonAI.DistributedNPCToonAI(air, npcId, questCallback=questCallback)
elif type == NPC_HQ: elif type == NPC_HQ:
@ -111,7 +112,7 @@ def createNPC(air, npcId, desc, zoneId, posIndex = 0, questCallback = None):
npc.setName(name) npc.setName(name)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
if dnaType == 'r': if dnaType == 'r':
dnaList = getRandomDNA(npcId, gender) dnaList = getRandomDNA(npcId, eyelashes)
else: else:
dnaList = dnaType dnaList = dnaType
if saveDNA: if saveDNA:
@ -139,7 +140,8 @@ def createNPC(air, npcId, desc, zoneId, posIndex = 0, questCallback = None):
rtDnaFile = open(RTDNAFile, 'w') rtDnaFile = open(RTDNAFile, 'w')
rtDnaFile.writelines(rtDNA) rtDnaFile.writelines(rtDNA)
rtDnaFile.close() rtDnaFile.close()
dna.newToonFromProperties(*dnaList)
dna.newToonFromProperties(*dnaList, isNPC=True)
npc.setDNAString(dna.makeNetString()) npc.setDNAString(dna.makeNetString())
npc.setHp(15) npc.setHp(15)
npc.setMaxHp(15) npc.setMaxHp(15)
@ -169,17 +171,17 @@ def createLocalNPC(npcId):
if npcId not in NPCToonDict: if npcId not in NPCToonDict:
return None return None
desc = NPCToonDict[npcId] desc = NPCToonDict[npcId]
canonicalZoneId, name, dnaType, gender, protected, type = desc canonicalZoneId, name, dnaType, eyelashes, protected, type = desc
npc = Toon.Toon() npc = Toon.Toon()
npc.setName(name) npc.setName(name)
npc.setPickable(0) npc.setPickable(0)
npc.setPlayerType(NametagGroup.CCNonPlayer) npc.setPlayerType(NametagGroup.CCNonPlayer)
dna = ToonDNA.ToonDNA() dna = ToonDNA.ToonDNA()
if dnaType == 'r': if dnaType == 'r':
dnaList = getRandomDNA(npcId, gender) dnaList = getRandomDNA(npcId, eyelashes)
else: else:
dnaList = dnaType dnaList = dnaType
dna.newToonFromProperties(*dnaList) dna.newToonFromProperties(*dnaList, isNPC=True)
npc.setDNAString(dna.makeNetString()) npc.setDNAString(dna.makeNetString())
npc.animFSM.request('neutral') npc.animFSM.request('neutral')
return npc return npc

View file

@ -10,11 +10,10 @@ class TailorClothesGUI(ClothesGUI.ClothesGUI):
def setupScrollInterface(self): def setupScrollInterface(self):
self.dna = self.toon.getStyle() self.dna = self.toon.getStyle()
gender = self.dna.getGender()
if self.swapEvent != None: if self.swapEvent != None:
self.tops = ToonDNA.getTops(gender, tailorId=self.tailorId) self.tops = ToonDNA.getTops(tailorId=self.tailorId)
self.bottoms = ToonDNA.getBottoms(gender, tailorId=self.tailorId) self.bottoms = ToonDNA.getBottoms(tailorId=self.tailorId)
self.gender = gender
self.topChoice = -1 self.topChoice = -1
self.bottomChoice = -1 self.bottomChoice = -1
self.setupButtons() self.setupButtons()

View file

@ -501,6 +501,7 @@ class Toon(Avatar.Avatar, ToonHead):
self.shoes = (0, 0, 0) self.shoes = (0, 0, 0)
self.isStunned = 0 self.isStunned = 0
self.isDisguised = 0 self.isDisguised = 0
self.eyelashes = 0
self.defaultColorScale = None self.defaultColorScale = None
self.jar = None self.jar = None
self.setTag('pieCode', str(ToontownGlobals.PieCodeToon)) self.setTag('pieCode', str(ToontownGlobals.PieCodeToon))
@ -610,7 +611,7 @@ class Toon(Avatar.Avatar, ToonHead):
return return
def updateToonDNA(self, newDNA, fForce = 0): def updateToonDNA(self, newDNA, fForce = 0):
self.style.gender = newDNA.getGender() self.style.eyelashes = newDNA.getEyelashes()
oldDNA = self.style oldDNA = self.style
if fForce or newDNA.head != oldDNA.head: if fForce or newDNA.head != oldDNA.head:
self.swapToonHead(newDNA.head) self.swapToonHead(newDNA.head)
@ -925,11 +926,11 @@ class Toon(Avatar.Avatar, ToonHead):
def generateToonClothes(self, fromNet = 0): def generateToonClothes(self, fromNet = 0):
swappedTorso = 0 swappedTorso = 0
if self.hasLOD(): if self.hasLOD():
if self.style.getGender() == 'f' and fromNet == 0: if fromNet == 0:
try: try:
bottomPair = ToonDNA.GirlBottoms[self.style.botTex] bottomPair = ToonDNA.Bottoms[self.style.botTex]
except: except:
bottomPair = ToonDNA.GirlBottoms[0] bottomPair = ToonDNA.Bottoms[0]
if len(self.style.torso) < 2: if len(self.style.torso) < 2:
self.sendLogSuspiciousEvent('nakedToonDNA %s was requested' % self.style.torso) self.sendLogSuspiciousEvent('nakedToonDNA %s was requested' % self.style.torso)
@ -972,25 +973,19 @@ class Toon(Avatar.Avatar, ToonHead):
except: except:
sleeveColor = ToonDNA.ClothesColors[0] sleeveColor = ToonDNA.ClothesColors[0]
if self.style.getGender() == 'm':
try:
texName = ToonDNA.BoyShorts[self.style.botTex]
except:
texName = ToonDNA.BoyShorts[0]
else:
try: try:
texName = ToonDNA.GirlBottoms[self.style.botTex][0] texName = ToonDNA.Bottoms[self.style.botTex][0]
except: except:
texName = ToonDNA.GirlBottoms[0][0] texName = ToonDNA.Bottoms[0][0]
bottomTex = loader.loadTexture(texName, okMissing=True) bottomTex = loader.loadTexture(texName, okMissing=True)
if bottomTex is None: if bottomTex is None:
self.sendLogSuspiciousEvent('failed to load texture %s' % texName) self.sendLogSuspiciousEvent('failed to load texture %s' % texName)
if self.style.getGender() == 'm':
bottomTex = loader.loadTexture(ToonDNA.BoyShorts[0]) bottomTex = loader.loadTexture(ToonDNA.Bottoms[0][0])
else:
bottomTex = loader.loadTexture(ToonDNA.GirlBottoms[0][0])
bottomTex.setMinfilter(Texture.FTLinearMipmapLinear) bottomTex.setMinfilter(Texture.FTLinearMipmapLinear)
bottomTex.setMagfilter(Texture.FTLinear) bottomTex.setMagfilter(Texture.FTLinear)
try: try:
@ -3039,6 +3034,9 @@ class Toon(Avatar.Avatar, ToonHead):
def exitScientistPlay(self): def exitScientistPlay(self):
self.stop() self.stop()
def setEyelashes(self, eyelashes):
self.eyelashes = eyelashes
loadModels() loadModels()
compileGlobalAnimList() compileGlobalAnimList()

File diff suppressed because it is too large Load diff

View file

@ -736,7 +736,7 @@ class ToonHead(Actor.Actor):
return reachedTarget return reachedTarget
def setupEyelashes(self, style): def setupEyelashes(self, style):
if style.getGender() == 'm': if style.getEyelashes():
if self.__eyelashOpen: if self.__eyelashOpen:
self.__eyelashOpen.removeNode() self.__eyelashOpen.removeNode()
self.__eyelashOpen = None self.__eyelashOpen = None

View file

@ -5381,6 +5381,7 @@ GenderShopGirlButtonText = 'Girl'
BodyShopHead = 'Head' BodyShopHead = 'Head'
BodyShopBody = 'Body' BodyShopBody = 'Body'
BodyShopLegs = 'Legs' BodyShopLegs = 'Legs'
BodyShopEyelashes = 'Eyelashes'
ColorShopToon = 'Toon Color' ColorShopToon = 'Toon Color'
ColorShopHead = 'Head' ColorShopHead = 'Head'
ColorShopBody = 'Body' ColorShopBody = 'Body'

View file

@ -129,7 +129,6 @@ NSnameResult = (0.09, 0.084, 0.084)
NSnameLabel = 0.1 NSnameLabel = 0.1
NStypeNotification = 0.08 NStypeNotification = 0.08
NScolorPrecede = True NScolorPrecede = True
MATenterGenderShop = 0.18
MATenterBodyShop = 0.18 MATenterBodyShop = 0.18
MATenterColorShop = 0.18 MATenterColorShop = 0.18
MATenterClothesShop = 0.16 MATenterClothesShop = 0.16

View file

@ -55,7 +55,7 @@ class ToonBase(OTPBase.OTPBase):
self.wantDynamicShadows = 0 self.wantDynamicShadows = 0
self.exitErrorCode = 0 self.exitErrorCode = 0
camera.setPosHpr(0, 0, 0, 0, 0, 0) camera.setPosHpr(0, 0, 0, 0, 0, 0)
self.camLens.setMinFov(ToontownGlobals.DefaultCameraFov / (4. / 3.)) self.camLens.setFov(ToontownGlobals.DefaultCameraFov)
self.camLens.setNearFar(ToontownGlobals.DefaultCameraNear, ToontownGlobals.DefaultCameraFar) self.camLens.setNearFar(ToontownGlobals.DefaultCameraNear, ToontownGlobals.DefaultCameraFar)
self.musicManager.setVolume(0.65) self.musicManager.setVolume(0.65)
self.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor) self.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)

View file

@ -63,10 +63,9 @@ ConfigVariableDouble('decompressor-step-time').setValue(0.01)
ConfigVariableDouble('extractor-step-time').setValue(0.01) ConfigVariableDouble('extractor-step-time').setValue(0.01)
backgroundNodePath = aspect2d.attachNewNode(backgroundNode, 0) backgroundNodePath = aspect2d.attachNewNode(backgroundNode, 0)
backgroundNodePath.setPos(0.0, 0.0, 0.0) backgroundNodePath.setPos(0.0, 0.0, 0.0)
backgroundNodePath.setScale(aspect2d, VBase3(1.33, 1, 1)) backgroundNodePath.setScale(render2d, VBase3(1))
backgroundNodePath.find('**/fg').setBin('fixed', 20) backgroundNodePath.find('**/fg').setBin('fixed', 20)
backgroundNodePath.find('**/bg').setBin('fixed', 10) backgroundNodePath.find('**/bg').setBin('fixed', 10)
backgroundNodePath.find('**/bg').setScale(aspect2d, VBase3(base.getAspectRatio(), 1, 1))
base.graphicsEngine.renderFrame() base.graphicsEngine.renderFrame()
DirectGuiGlobals.setDefaultRolloverSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_rollover.ogg')) DirectGuiGlobals.setDefaultRolloverSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_rollover.ogg'))
DirectGuiGlobals.setDefaultClickSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_create_toon_fwd.ogg')) DirectGuiGlobals.setDefaultClickSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_create_toon_fwd.ogg'))
@ -90,7 +89,7 @@ else:
from direct.gui.DirectGui import OnscreenText from direct.gui.DirectGui import OnscreenText
serverVersion = ConfigVariableString('server-version', 'no_version_set').value serverVersion = ConfigVariableString('server-version', 'no_version_set').value
print('ToontownStart: serverVersion: ', serverVersion) print('ToontownStart: serverVersion: ', serverVersion)
version = OnscreenText(serverVersion, parent=base.a2dBottomLeft, pos=(0.033, 0.025), scale=0.06, fg=Vec4(0, 0, 1, 0.6), align=TextNode.ALeft) version = OnscreenText(serverVersion, pos=(-1.3, -0.975), scale=0.06, fg=Vec4(0, 0, 1, 0.6), align=TextNode.ALeft)
loader.beginBulkLoad('init', TTLocalizer.LoaderLabel, 138, 0, TTLocalizer.TIP_NONE) loader.beginBulkLoad('init', TTLocalizer.LoaderLabel, 138, 0, TTLocalizer.TIP_NONE)
from toontown.distributed.ToontownClientRepository import ToontownClientRepository from toontown.distributed.ToontownClientRepository import ToontownClientRepository
cr = ToontownClientRepository(serverVersion, launcher) cr = ToontownClientRepository(serverVersion, launcher)

View file

@ -41,7 +41,6 @@ class ToontownLoadingScreen:
self.waitBar.reparentTo(self.gui) self.waitBar.reparentTo(self.gui)
self.title.reparentTo(self.gui) self.title.reparentTo(self.gui)
self.gui.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX) self.gui.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
self.gui.find('**/bg').setScale(aspect2dp, VBase3(base.getAspectRatio(), 1, 1))
else: else:
self.waitBar.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX) self.waitBar.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
self.title.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX) self.title.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)

View file

@ -0,0 +1,140 @@
from direct.directnotify.DirectNotifyGlobal import directNotify
from toontown.uberdog import DataStoreGlobals
from direct.showbase.DirectObject import DirectObject
import pickle
class DataStoreAIClient(DirectObject):
"""
This class should be instantiated by any class that needs to
access an Uberdog data store.
The client, as it is now, has the ability to create and destroy
DataStores on the Uberdog. This is mainly provided for backwards
compatibility with the Toontown architecture where the logic has
already been written for the AI side of things.
For example, the HolidayManagerAI is something that could feasably
be run on the Uberdog, however it's already well established on
the AI. For this reason, we'll allow the HolidayManagerAI to
create and destroy data stores as it needs to.
All it takes is one request to the Uberdog to carry out one of
these operations. Any further requests for data to an already
destroyed store will go unanswered.
In the future, we should make attempts to keep the create/destroy
control on the Uberdog. That way, we have only one point of control
rather than several various AIs who may not be entirely in sync.
"""
notify = directNotify.newCategory('DataStoreAIClient')
wantDsm = simbase.config.GetBool('want-ddsm', 1)
def __init__(self,air,storeId,resultsCallback):
"""
storeId is a unique identifier to the type of store
the client wishes to connect to. There will only be
one store of this type on the Uberdog at any given time.
resultsCallback is a function that accepts one argument,
the results returned from a query. The format of this
result argument is defined in the store's class definition.
"""
if self.wantDsm:
self.__storeMgr = air.dataStoreManager
self.__storeId = storeId
self.__resultsCallback = resultsCallback
self.__storeClass = DataStoreGlobals.getStoreClass(storeId)
self.__queryTypesDict = self.__storeClass.QueryTypes
self.__queryStringDict = dict(list(zip(list(self.__queryTypesDict.values()),
list(self.__queryTypesDict.keys()))))
self.__enabled = False
def openStore(self):
"""
Attempt to connect to the store defined by the storeId in the
__init__() function. If no store of this type is present on
the Uberdog, the store is created at this time. Queries can now
be sent to the store and replies from the store will be processed
by the client.
"""
if self.wantDsm:
self.__storeMgr.startStore(self.__storeId)
self.__startClient()
def closeStore(self):
"""
This client will no longer receive results from the store. Also,
the store, if present on the Uberdog, will now be shutdown and all
data destroyed. Do not use this method unless you are sure that
the data is no longer needed by this, or any other, client.
"""
if self.wantDsm:
self.__stopClient()
self.__storeMgr.stopStore(self.__storeId)
def isOpen(self):
return self.__enabled
def getQueryTypes(self):
return list(self.__queryTypesDict.keys())
def getQueryTypeString(self,qId):
return self.__queryStringDict.get(qId,None)
def sendQuery(self,queryTypeString,queryData):
"""
Sends a query to the data store. The format of the query is
defined in the store's class definition.
"""
if self.__enabled:
qId = self.__queryTypesDict.get(queryTypeString,None)
if qId is not None:
query = (qId,queryData)
# pack the data to be sent to the Uberdog store.
pQuery = pickle.dumps(query)
self.__storeMgr.queryStore(self.__storeId,pQuery)
else:
self.notify.debug('Tried to send invalid query type: \'%s\'' % (queryTypeString,))
else:
self.notify.warning('Client currently stopped. \'%s\' query will fail.' % (queryTypeString,))
def receiveResults(self,data):
"""
Upon receiving a query, the store will respond with a result.
This function will call the resultsCallback function with the
result data as its sole argument. Try to treat the
resultsCallback function as an event that is fired whenever
the client receives data from the store.
"""
# unpack the results from the Uberdog store.
if data == 'Store not found':
self.notify.debug('%s not present on uberdog. Query dropped.' %(self.__storeClass.__name__,))
else:
results = pickle.loads(data)
self.__resultsCallback(results)
def __startClient(self):
"""
Allow the client to send queries and receive results from its
associated data store.
"""
self.accept('TDS-results-%d'%self.__storeId,self.receiveResults)
self.__enabled = True
def __stopClient(self):
"""
Disallow the client from sending queries and receiving results
from its associated data store.
"""
self.ignoreAll()
self.__enabled = False
def deleteBackupStores(self):
"""
Delete any backed up stores from previous year's
"""
if self.wantDsm:
self.__storeMgr.deleteBackupStores()

View file

@ -17,6 +17,7 @@ class ToontownUDRepository(ToontownInternalRepository):
ToontownInternalRepository.__init__(self, baseChannel, serverId, dcSuffix='UD') ToontownInternalRepository.__init__(self, baseChannel, serverId, dcSuffix='UD')
self.toontownTimeManager = None self.toontownTimeManager = None
self.astronLoginManager = None self.astronLoginManager = None
self.toontownFriendsManager = None
def handleConnected(self): def handleConnected(self):
ToontownInternalRepository.handleConnected(self) ToontownInternalRepository.handleConnected(self)
@ -45,5 +46,7 @@ class ToontownUDRepository(ToontownInternalRepository):
if __astron__: if __astron__:
# Create our Astron login manager... # Create our Astron login manager...
self.astronLoginManager = self.generateGlobalObject(OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager') self.astronLoginManager = self.generateGlobalObject(OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
self.chatHandler = self.generateGlobalObject(OTP_DO_ID_CHAT_ROUTER, 'ChatHandler')
self.avatarFriendsManager = self.generateGlobalObject(OTP_DO_ID_AVATAR_FRIENDS_MANAGER, 'AvatarFriendsManager') # Create our Toontown friends manager... (Astron specific)
self.toontownFriendsManager = self.generateGlobalObject(OTP_DO_ID_TOONTOWN_FRIENDS_MANAGER, 'ToontownFriendsManager')
self.chatHandler = self.generateGlobalObject(OTP_DO_ID_CHAT_HANDLER, 'ChatHandler')

View file

@ -5,10 +5,7 @@ cd..
rem Read the contents of PPYTHON_PATH into %PPYTHON_PATH%: rem Read the contents of PPYTHON_PATH into %PPYTHON_PATH%:
set /P PPYTHON_PATH=<PPYTHON_PATH set /P PPYTHON_PATH=<PPYTHON_PATH
set /P GAME_SERVER="Game Server (Port is typically 7198): " set /P LOGIN_TOKEN="Secret token: "
set /P LOGIN_TOKEN="Login password: "
:GAME
%PPYTHON_PATH% -m toontown.launcher.QuickStartLauncher %PPYTHON_PATH% -m toontown.launcher.QuickStartLauncher
pause pause
GOTO GAME