ai: Attempts to connect now
This commit is contained in:
parent
aaafd22c6d
commit
54cd9c8736
|
|
@ -0,0 +1,596 @@
|
||||||
|
from .AIBaseGlobal import *
|
||||||
|
from pandac.PandaModules import *
|
||||||
|
from direct.distributed import DistributedObjectAI
|
||||||
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
|
from otp.otpbase import OTPGlobals
|
||||||
|
from direct.showbase import PythonUtil, GarbageReport, ContainerReport, MessengerLeakDetector
|
||||||
|
from direct.showbase import ContainerLeakDetector
|
||||||
|
from direct.showbase.PythonUtil import Functor, DelayedCall, formatTimeCompact
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
from direct.task import Task
|
||||||
|
|
||||||
|
class MagicWordManagerAI(DistributedObjectAI.DistributedObjectAI):
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory("MagicWordManagerAI")
|
||||||
|
|
||||||
|
supportSuperchat = simbase.config.GetBool('support-superchat', 0)
|
||||||
|
supportRename = simbase.config.GetBool('support-rename', 0)
|
||||||
|
|
||||||
|
# Fill in by subclass
|
||||||
|
GameAvatarClass = None
|
||||||
|
|
||||||
|
# This will hold the local namespace we evaluate '~ai' messages
|
||||||
|
# within.
|
||||||
|
ExecNamespace = { }
|
||||||
|
|
||||||
|
def __init__(self, air):
|
||||||
|
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
||||||
|
|
||||||
|
def setMagicWord(self, word, avId, zoneId, signature):
|
||||||
|
senderId = self.air.getAvatarIdFromSender()
|
||||||
|
|
||||||
|
sender = self.air.doId2do.get(senderId, None)
|
||||||
|
if sender:
|
||||||
|
if senderId == avId:
|
||||||
|
sender = "%s/%s(%s)" % (sender.accountName, sender.name, senderId)
|
||||||
|
else:
|
||||||
|
sender = "%s/%s(%s) (for %d)" % (sender.accountName, sender.name, senderId, avId)
|
||||||
|
else:
|
||||||
|
sender = "Unknown avatar %d" % (senderId)
|
||||||
|
|
||||||
|
self.notify.info("%s (%s) just said the magic word: %s" % (sender, signature, word))
|
||||||
|
self.air.writeServerEvent('magic-word', senderId, "%s|%s|%s" % (sender, signature, word))
|
||||||
|
if self.air.doId2do.has_key(avId):
|
||||||
|
av = self.air.doId2do[avId]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.doMagicWord(word, av, zoneId, senderId)
|
||||||
|
except:
|
||||||
|
response = PythonUtil.describeException(backTrace = 1)
|
||||||
|
self.notify.warning("Ignoring error in magic word:\n%s" % response)
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
else:
|
||||||
|
self.notify.info("Don't know avatar %d." % (avId))
|
||||||
|
|
||||||
|
def wordIs(self, word, w):
|
||||||
|
return word == w or word[:(len(w)+1)] == ('%s ' % w)
|
||||||
|
|
||||||
|
def getWordIs(self, word):
|
||||||
|
# bind a word to self.wordIs and return a callable obj
|
||||||
|
return Functor(self.wordIs, word)
|
||||||
|
|
||||||
|
def doMagicWord(self, word, av, zoneId, senderId):
|
||||||
|
wordIs = self.getWordIs(word)
|
||||||
|
|
||||||
|
if wordIs("~rename"):
|
||||||
|
if (not self.supportRename):
|
||||||
|
self.notify.warning("Rename is not supported for %s, requested by %d" % (av.name, senderId))
|
||||||
|
else:
|
||||||
|
name = string.strip(word[8:])
|
||||||
|
if name == "":
|
||||||
|
response = "No name."
|
||||||
|
else:
|
||||||
|
av.d_setName(name)
|
||||||
|
|
||||||
|
elif wordIs("~badname"):
|
||||||
|
self.notify.warning("Renaming inappropriately named toon %s (doId %d)." % (av.name, av.doId))
|
||||||
|
name = "toon%d" % (av.doId % 1000000)
|
||||||
|
av.d_setName(name)
|
||||||
|
|
||||||
|
elif wordIs("~chat"):
|
||||||
|
if (not self.supportSuperchat) and (senderId != av.doId):
|
||||||
|
self.notify.warning("Super chat is not supported for %s, requested by %d" % (av.name, senderId))
|
||||||
|
else:
|
||||||
|
av.d_setCommonChatFlags(OTPGlobals.CommonChat)
|
||||||
|
self.notify.debug("Giving common chat permission to " + av.name)
|
||||||
|
elif wordIs("~superchat"):
|
||||||
|
if not self.supportSuperchat:
|
||||||
|
self.notify.warning("Super chat is not supported for " + av.name)
|
||||||
|
else:
|
||||||
|
av.d_setCommonChatFlags(OTPGlobals.SuperChat)
|
||||||
|
self.notify.debug("Giving super chat permission to " + av.name)
|
||||||
|
elif wordIs("~nochat"):
|
||||||
|
av.d_setCommonChatFlags(0)
|
||||||
|
self.notify.debug("Removing special chat permissions for " + av.name)
|
||||||
|
|
||||||
|
elif wordIs("~listen"):
|
||||||
|
if (not self.supportSuperchat) and (senderId != av.doId):
|
||||||
|
self.notify.warning("Listen is not supported for %s, requested by %d" % (av.name, senderId))
|
||||||
|
else:
|
||||||
|
# This is a client-side word.
|
||||||
|
if (senderId != av.doId):
|
||||||
|
self.sendUpdateToAvatarId(av.doId, 'setMagicWord', [word, av.doId, zoneId])
|
||||||
|
|
||||||
|
elif wordIs("~fix"):
|
||||||
|
anyChanged = av.fixAvatar()
|
||||||
|
if anyChanged:
|
||||||
|
response = "avatar fixed."
|
||||||
|
else:
|
||||||
|
response = "avatar does not need fixing."
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs("~who all"):
|
||||||
|
str = ''
|
||||||
|
for obj in self.air.doId2do.values():
|
||||||
|
if hasattr(obj, "accountName"):
|
||||||
|
str += '%s %s\n' % (obj.accountName, obj.name)
|
||||||
|
if not str:
|
||||||
|
str = "No avatars."
|
||||||
|
self.down_setMagicWordResponse(senderId, str)
|
||||||
|
|
||||||
|
elif wordIs("~ouch"):
|
||||||
|
if av.hp < 1:
|
||||||
|
av.b_setHp(0)
|
||||||
|
av.toonUp(1)
|
||||||
|
else:
|
||||||
|
av.b_setHp(1)
|
||||||
|
self.notify.debug("Only 1 hp for " + av.name)
|
||||||
|
elif wordIs("~sad"):
|
||||||
|
av.b_setHp(0)
|
||||||
|
self.notify.debug("Only 0 hp for " + av.name)
|
||||||
|
elif wordIs("~dead"):
|
||||||
|
av.takeDamage(av.hp)
|
||||||
|
self.notify.debug(av.name + " is dead")
|
||||||
|
elif wordIs("~waydead"):
|
||||||
|
av.takeDamage(av.hp)
|
||||||
|
av.b_setHp(-100)
|
||||||
|
self.notify.debug(av.name + " is way dead")
|
||||||
|
elif wordIs("~toonup"):
|
||||||
|
av.toonUp(av.maxHp)
|
||||||
|
self.notify.debug("Full heal for " + av.name)
|
||||||
|
elif wordIs('~hp'):
|
||||||
|
args = word.split()
|
||||||
|
hp = int(args[1])
|
||||||
|
av.b_setHp(hp)
|
||||||
|
self.notify.debug('Set hp to %s for %s' % (hp, av.name))
|
||||||
|
|
||||||
|
elif wordIs("~ainotify"):
|
||||||
|
args = word.split()
|
||||||
|
n = Notify.ptr().getCategory(args[1])
|
||||||
|
n.setSeverity(
|
||||||
|
{'error': NSError,
|
||||||
|
'warning': NSWarning,
|
||||||
|
'info': NSInfo,
|
||||||
|
'debug': NSDebug,
|
||||||
|
'spam': NSSpam,}[args[2]])
|
||||||
|
|
||||||
|
elif wordIs("~ghost"):
|
||||||
|
# Toggle ghost mode. Ghost mode == 2 indicates a magic
|
||||||
|
# word was the source.
|
||||||
|
if av.ghostMode:
|
||||||
|
av.b_setGhostMode(0)
|
||||||
|
else:
|
||||||
|
av.b_setGhostMode(2)
|
||||||
|
|
||||||
|
elif wordIs('~immortal'):
|
||||||
|
# ~immortal toggles immortal mode on and off
|
||||||
|
# ~immortal 0/1 and ~immortal on/off sets the mode explicitly
|
||||||
|
args = word.split()
|
||||||
|
invalid = False
|
||||||
|
if len(args) > 1 and args[1] in ('0', 'off'):
|
||||||
|
immortal = False
|
||||||
|
elif len(args) > 1 and args[1] in ('1', 'on'):
|
||||||
|
immortal = True
|
||||||
|
elif len(args) > 1:
|
||||||
|
invalid = True
|
||||||
|
else:
|
||||||
|
immortal = not av.immortalMode
|
||||||
|
|
||||||
|
if invalid:
|
||||||
|
self.down_setMagicWordResponse(senderId, 'unknown argument %s' % args[1])
|
||||||
|
else:
|
||||||
|
# immortality
|
||||||
|
av.setImmortalMode(immortal)
|
||||||
|
if av.immortalMode:
|
||||||
|
response = 'immortality ON'
|
||||||
|
else:
|
||||||
|
response = 'immortality OFF'
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs("~dna"):
|
||||||
|
# Fiddle with your dna.
|
||||||
|
self.doDna(word, av, zoneId, senderId)
|
||||||
|
|
||||||
|
elif wordIs('~ai'):
|
||||||
|
# Execute an arbitrary Python command on the AI.
|
||||||
|
command = string.strip(word[3:])
|
||||||
|
self.notify.warning("Executing command '%s' from %s" % (command, senderId))
|
||||||
|
text = self.__execMessage(command)[:simbase.config.GetInt("ai-debug-length",300)]
|
||||||
|
self.down_setMagicWordResponse(
|
||||||
|
senderId, text)
|
||||||
|
|
||||||
|
elif wordIs('~ud'):
|
||||||
|
# Execute an arbitrary Python command on the ud.
|
||||||
|
print(word)
|
||||||
|
channel,command = re.match("~ud ([0-9]+) (.+)", word).groups()
|
||||||
|
channel = int(channel)
|
||||||
|
if(simbase.air.doId2do.get(channel)):
|
||||||
|
self.notify.warning("Passing command '%s' to %s from %s" % (command, channel, senderId))
|
||||||
|
|
||||||
|
try:
|
||||||
|
simbase.air.doId2do[channel].sendUpdate("execCommand", [command, self.doId, senderId, zoneId])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif wordIs('~aiobjects'):
|
||||||
|
args = word.split()
|
||||||
|
from direct.showbase import ObjectReport
|
||||||
|
report = ObjectReport.ObjectReport('AI ~objects')
|
||||||
|
|
||||||
|
if 'all' in args:
|
||||||
|
self.notify.info('printing full object set...')
|
||||||
|
report.getObjectPool().printObjsByType(printReferrers='ref' in args)
|
||||||
|
|
||||||
|
if hasattr(self, 'baselineObjReport'):
|
||||||
|
self.notify.info('calculating diff from baseline ObjectReport...')
|
||||||
|
self.lastDiff = self.baselineObjReport.diff(report)
|
||||||
|
self.lastDiff.printOut(full=('diff' in args or 'dif' in args))
|
||||||
|
|
||||||
|
if 'baseline' in args or not hasattr(self, 'baselineObjReport'):
|
||||||
|
self.notify.info('recording baseline ObjectReport...')
|
||||||
|
if hasattr(self, 'baselineObjReport'):
|
||||||
|
self.baselineObjReport.destroy()
|
||||||
|
self.baselineObjReport = report
|
||||||
|
|
||||||
|
self.down_setMagicWordResponse(senderId, 'objects logged')
|
||||||
|
|
||||||
|
elif wordIs('~aiobjecthg'):
|
||||||
|
import gc
|
||||||
|
objs = gc.get_objects()
|
||||||
|
type2count = {}
|
||||||
|
for obj in objs:
|
||||||
|
tn = safeTypeName(obj)
|
||||||
|
type2count.setdefault(tn, 0)
|
||||||
|
type2count[tn] += 1
|
||||||
|
count2type = invertDictLossless(type2count)
|
||||||
|
counts = count2type.keys()
|
||||||
|
counts.sort()
|
||||||
|
counts.reverse()
|
||||||
|
for count in counts:
|
||||||
|
print('%s: %s' % (count, count2type[count]))
|
||||||
|
self.down_setMagicWordResponse(senderId, '~aiobjecthg complete')
|
||||||
|
|
||||||
|
elif wordIs('~aicrash'):
|
||||||
|
# TODO: require a typed explanation in production
|
||||||
|
# if we call notify.error directly, the magic word mgr will catch it
|
||||||
|
# self.notify.error doesn't seem to work either
|
||||||
|
DelayedCall(Functor(simbase.air.notify.error, '~aicrash: simulating an AI crash'))
|
||||||
|
|
||||||
|
elif wordIs('~aicontainers'):
|
||||||
|
args = word.split()
|
||||||
|
limit = 30
|
||||||
|
if 'full' in args:
|
||||||
|
limit = None
|
||||||
|
ContainerReport.ContainerReport('~aicontainers', log=True, limit=limit, threaded=True)
|
||||||
|
|
||||||
|
elif wordIs('~aigarbage'):
|
||||||
|
args = word.split()
|
||||||
|
# it can take a LOOONG time to print out the garbage referrers and referents
|
||||||
|
# by reference (as opposed to by number)
|
||||||
|
full = ('full' in args)
|
||||||
|
safeMode = ('safe' in args)
|
||||||
|
verbose = ('verbose' in args)
|
||||||
|
delOnly = ('delonly' in args)
|
||||||
|
def handleGarbageDone(senderId, garbageReport):
|
||||||
|
self.down_setMagicWordResponse(senderId, 'garbage logged, %s AI cycles' % garbageReport.getNumCycles())
|
||||||
|
# This does a garbage collection and dumps the list of leaked (uncollectable) objects to the AI log.
|
||||||
|
GarbageReport.GarbageReport('~aigarbage', fullReport=full, verbose=verbose, log=True, threaded=True,
|
||||||
|
doneCallback=Functor(handleGarbageDone, senderId), safeMode=safeMode, delOnly=delOnly)
|
||||||
|
|
||||||
|
elif wordIs("~creategarbage"):
|
||||||
|
args = word.split()
|
||||||
|
num = 1
|
||||||
|
if len(args) > 1:
|
||||||
|
num = int(args[1])
|
||||||
|
GarbageReport._createGarbage(num)
|
||||||
|
self.down_setMagicWordResponse(senderId, 'leaked garbage created')
|
||||||
|
|
||||||
|
elif wordIs('~leaktask'):
|
||||||
|
def leakTask(task):
|
||||||
|
return task.cont
|
||||||
|
taskMgr.add(leakTask, uniqueName('leakedTask'))
|
||||||
|
leakTask = None
|
||||||
|
self.down_setMagicWordResponse(senderId, 'leaked task created')
|
||||||
|
|
||||||
|
elif wordIs('~aileakmessage'):
|
||||||
|
MessengerLeakDetector._leakMessengerObject()
|
||||||
|
self.down_setMagicWordResponse(senderId, 'messenger leak object created')
|
||||||
|
|
||||||
|
elif wordIs('~leakContainer'):
|
||||||
|
ContainerLeakDetector._createContainerLeak()
|
||||||
|
self.down_setMagicWordResponse(senderId, 'leak container task created')
|
||||||
|
|
||||||
|
elif wordIs('~aipstats'):
|
||||||
|
args = word.split()
|
||||||
|
hostname = None
|
||||||
|
port = None
|
||||||
|
if len(args) > 1:
|
||||||
|
hostname = args[1]
|
||||||
|
if len(args) > 2:
|
||||||
|
port = int(args[2])
|
||||||
|
# make sure pstats is enabled
|
||||||
|
simbase.wantStats = 1
|
||||||
|
Task.TaskManager.pStatsTasks = 1
|
||||||
|
result = simbase.createStats(hostname, port)
|
||||||
|
connectionName = '%s' % hostname
|
||||||
|
if port is not None:
|
||||||
|
connectionName += ':%s' % port
|
||||||
|
if result:
|
||||||
|
response = 'connected AI pstats to %s' % connectionName
|
||||||
|
else:
|
||||||
|
response = 'could not connect AI pstats to %s' % connectionName
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~aiprofile'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1:
|
||||||
|
num = int(args[1])
|
||||||
|
else:
|
||||||
|
num = 5
|
||||||
|
session = taskMgr.getProfileSession('~aiprofile')
|
||||||
|
session.setLogAfterProfile(True)
|
||||||
|
taskMgr.profileFrames(num, session)
|
||||||
|
self.down_setMagicWordResponse(senderId, 'profiling %s AI frames...' % num)
|
||||||
|
|
||||||
|
elif wordIs('~aiframeprofile'):
|
||||||
|
args = word.split()
|
||||||
|
wasOn = bool(taskMgr.getProfileFrames())
|
||||||
|
if len(args) > 1:
|
||||||
|
setting = bool(int(args[1]))
|
||||||
|
else:
|
||||||
|
setting = not wasOn
|
||||||
|
taskMgr.setProfileFrames(setting)
|
||||||
|
self.down_setMagicWordResponse(
|
||||||
|
senderId,
|
||||||
|
'AI frame profiling %s%s' % (choice(setting, 'ON', 'OFF'),
|
||||||
|
choice(wasOn == setting, ' already', '')))
|
||||||
|
|
||||||
|
elif wordIs('~aitaskprofile'):
|
||||||
|
args = word.split()
|
||||||
|
wasOn = bool(taskMgr.getProfileTasks())
|
||||||
|
if len(args) > 1:
|
||||||
|
setting = bool(int(args[1]))
|
||||||
|
else:
|
||||||
|
setting = not wasOn
|
||||||
|
taskMgr.setProfileTasks(setting)
|
||||||
|
self.down_setMagicWordResponse(
|
||||||
|
senderId,
|
||||||
|
'AI task profiling %s%s' % (choice(setting, 'ON', 'OFF'),
|
||||||
|
choice(wasOn == setting, ' already', '')))
|
||||||
|
|
||||||
|
elif wordIs('~aitaskspikethreshold'):
|
||||||
|
from direct.task.TaskProfiler import TaskProfiler
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1:
|
||||||
|
threshold = float(args[1])
|
||||||
|
response = 'AI task spike threshold set to %ss' % threshold
|
||||||
|
else:
|
||||||
|
threshold = TaskProfiler.GetDefaultSpikeThreshold()
|
||||||
|
response = 'AI task spike threshold reset to %ss' % threshold
|
||||||
|
TaskProfiler.SetSpikeThreshold(threshold)
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~ailogtaskprofiles'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1:
|
||||||
|
name = args[1]
|
||||||
|
else:
|
||||||
|
name = None
|
||||||
|
taskMgr.logTaskProfiles(name)
|
||||||
|
response = 'logged AI task profiles%s' % choice(name, ' for %s' % name, '')
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~aitaskprofileflush'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1:
|
||||||
|
name = args[1]
|
||||||
|
else:
|
||||||
|
name = None
|
||||||
|
taskMgr.flushTaskProfiles(name)
|
||||||
|
response = 'flushed AI task profiles%s' % choice(name, ' for %s' % name, '')
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~aiobjectcount'):
|
||||||
|
simbase.air.printObjectCount()
|
||||||
|
self.down_setMagicWordResponse(senderId, 'logging AI distributed object count...')
|
||||||
|
|
||||||
|
elif wordIs('~aitaskmgr'):
|
||||||
|
print(taskMgr)
|
||||||
|
self.down_setMagicWordResponse(senderId, 'logging AI taskMgr...')
|
||||||
|
|
||||||
|
elif wordIs('~aijobmgr'):
|
||||||
|
print(jobMgr)
|
||||||
|
self.down_setMagicWordResponse(senderId, 'logging AI jobMgr...')
|
||||||
|
|
||||||
|
elif wordIs('~aijobtime'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1:
|
||||||
|
time = float(args[1])
|
||||||
|
else:
|
||||||
|
time = None
|
||||||
|
response = ''
|
||||||
|
if time is None:
|
||||||
|
time = jobMgr.getDefaultTimeslice()
|
||||||
|
time = time * 1000.
|
||||||
|
response = 'reset AI jobMgr timeslice to %s ms' % time
|
||||||
|
else:
|
||||||
|
response = 'set AI jobMgr timeslice to %s ms' % time
|
||||||
|
time = time / 1000.
|
||||||
|
jobMgr.setTimeslice(time)
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~aidetectleaks'):
|
||||||
|
started = self.air.startLeakDetector()
|
||||||
|
self.down_setMagicWordResponse(senderId,
|
||||||
|
choice(started,
|
||||||
|
'AI leak detector started',
|
||||||
|
'AI leak detector already started',
|
||||||
|
))
|
||||||
|
|
||||||
|
elif wordIs('~aitaskthreshold'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1.:
|
||||||
|
threshold = float(args[1])
|
||||||
|
else:
|
||||||
|
threshold = None
|
||||||
|
response = ''
|
||||||
|
if threshold is None:
|
||||||
|
threshold = taskMgr.DefTaskDurationWarningThreshold
|
||||||
|
response = 'reset AI task duration warning threshold to %s' % threshold
|
||||||
|
else:
|
||||||
|
response = 'set AI task duration warning threshold to %s' % threshold
|
||||||
|
taskMgr.setTaskDurationWarningThreshold(threshold)
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~aimessenger'):
|
||||||
|
print(messenger)
|
||||||
|
self.down_setMagicWordResponse(senderId, 'logging AI messenger...')
|
||||||
|
|
||||||
|
elif wordIs('~requestdeleted'):
|
||||||
|
requestDeletedDOs = self.air.getRequestDeletedDOs()
|
||||||
|
response = '%s requestDeleted AI objects%s' % (
|
||||||
|
len(requestDeletedDOs), choice(len(requestDeletedDOs), ', logging...', ''))
|
||||||
|
s = '~requestDeleted: ['
|
||||||
|
for do, age in requestDeletedDOs:
|
||||||
|
s += '[%s, %s]' % (do.__class__.__name__, age)
|
||||||
|
s += ']'
|
||||||
|
self.notify.info(s)
|
||||||
|
if len(requestDeletedDOs):
|
||||||
|
response += '\noldest: %s, %s' % (
|
||||||
|
requestDeletedDOs[0][0].__class__.__name__,
|
||||||
|
formatTimeCompact(requestDeletedDOs[0][1]))
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
|
||||||
|
elif wordIs('~aigptc'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1. and hasattr(self.cr, 'leakDetector'):
|
||||||
|
gptcJob = self.cr.leakDetector.getPathsToContainers(
|
||||||
|
'~aigptc', args[1], Functor(self._handleGPTCfinished, senderId, args[1]))
|
||||||
|
else:
|
||||||
|
self.down_setMagicWordResponse(senderId, 'error')
|
||||||
|
|
||||||
|
elif wordIs('~aigptcn'):
|
||||||
|
args = word.split()
|
||||||
|
if len(args) > 1. and hasattr(self.cr, 'leakDetector'):
|
||||||
|
gptcnJob = self.cr.leakDetector.getPathsToContainersNamed(
|
||||||
|
'~aigptcn', args[1], Functor(self._handleGPTCNfinished, senderId, args[1]))
|
||||||
|
else:
|
||||||
|
self.down_setMagicWordResponse(senderId, 'error')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# The word is not an AI-side magic word. If the sender is
|
||||||
|
# different than the target avatar, then pass the magic
|
||||||
|
# word down to the target client-side MagicWordManager to
|
||||||
|
# execute a client-side magic word.
|
||||||
|
# MPG this gets done in child class
|
||||||
|
#if (senderId != av.doId):
|
||||||
|
# self.sendUpdateToAvatarId(av.doId, 'setMagicWord', [word, av.doId, zoneId])
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# MPG define in child class
|
||||||
|
"""
|
||||||
|
def doDna(self, word, av, zoneId, senderId):
|
||||||
|
# Handle the ~dna magic word: change your dna
|
||||||
|
|
||||||
|
# Strip of the "~dna" part; everything else is parameters to
|
||||||
|
# AvatarDNA.updateToonProperties.
|
||||||
|
parms = string.strip(word[4:])
|
||||||
|
|
||||||
|
# Get a copy of the avatar's current DNA.
|
||||||
|
dna = ToonDNA.ToonDNA(av.dna.makeNetString())
|
||||||
|
|
||||||
|
# Modify it according to the user's parameter selection.
|
||||||
|
eval("dna.updateToonProperties(%s)" % (parms))
|
||||||
|
|
||||||
|
av.b_setDNAString(dna.makeNetString())
|
||||||
|
response = "%s" % (dna.asTuple(),)
|
||||||
|
|
||||||
|
self.down_setMagicWordResponse(senderId, response)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _handleGPTCfinished(self, senderId, ct, gptcJob):
|
||||||
|
self.down_setMagicWordResponse(senderId, 'aigptc(%s) finished' % ct)
|
||||||
|
|
||||||
|
def _handleGPTCNfinished(self, senderId, cn, gptcnJob):
|
||||||
|
self.down_setMagicWordResponse(senderId, 'aigptcn(%s) finished' % cn)
|
||||||
|
|
||||||
|
def __execMessage(self, message):
|
||||||
|
if not self.ExecNamespace:
|
||||||
|
# Import some useful variables into the ExecNamespace initially.
|
||||||
|
exec('from pandac.PandaModules import *' in globals(), self.ExecNamespace)
|
||||||
|
#self.importExecNamespace()
|
||||||
|
|
||||||
|
# Now try to evaluate the expression using ChatInputNormal.ExecNamespace as
|
||||||
|
# the local namespace.
|
||||||
|
try:
|
||||||
|
return str(eval(message, globals(), self.ExecNamespace))
|
||||||
|
|
||||||
|
except SyntaxError:
|
||||||
|
# Maybe it's only a statement, like "x = 1", or
|
||||||
|
# "import math". These aren't expressions, so eval()
|
||||||
|
# fails, but they can be exec'ed.
|
||||||
|
try:
|
||||||
|
exec(message in globals(), self.ExecNamespace)
|
||||||
|
return 'ok'
|
||||||
|
except:
|
||||||
|
exception = sys.exc_info()[0]
|
||||||
|
extraInfo = sys.exc_info()[1]
|
||||||
|
if extraInfo:
|
||||||
|
return str(extraInfo)
|
||||||
|
else:
|
||||||
|
return str(exception)
|
||||||
|
except:
|
||||||
|
exception = sys.exc_info()[0]
|
||||||
|
extraInfo = sys.exc_info()[1]
|
||||||
|
if extraInfo:
|
||||||
|
return str(extraInfo)
|
||||||
|
else:
|
||||||
|
return str(exception)
|
||||||
|
|
||||||
|
def down_setMagicWordResponse(self, avId, response):
|
||||||
|
"""down_setMagicWordResponse(self, avId, string response)
|
||||||
|
|
||||||
|
Send a response to the avatar who said the magic word.
|
||||||
|
"""
|
||||||
|
self.sendUpdateToAvatarId(avId, 'setMagicWordResponse', [response])
|
||||||
|
|
||||||
|
def setWho(self, avIds):
|
||||||
|
# Sent by the client in response to ~who.
|
||||||
|
str = ''
|
||||||
|
for avId in avIds:
|
||||||
|
obj = self.air.doId2do.get(avId, None)
|
||||||
|
if not obj:
|
||||||
|
self.air.writeServerEvent('suspicious', avId, 'MagicWordManager.setWho not a valid avId: %s' % avId)
|
||||||
|
return
|
||||||
|
elif obj.__class__ == self.GameAvatarClass:
|
||||||
|
str += '%s %s\n' % (obj.accountName, obj.name)
|
||||||
|
if not str:
|
||||||
|
str = "No avatars."
|
||||||
|
|
||||||
|
senderId = self.air.getAvatarIdFromSender()
|
||||||
|
self.down_setMagicWordResponse(senderId, str)
|
||||||
|
|
||||||
|
class FakeAv:
|
||||||
|
# fake avatar object that we can pass in to prevent magic words from crashing
|
||||||
|
def __init__(self, senderId):
|
||||||
|
self.hp = 100
|
||||||
|
self.doId = senderId
|
||||||
|
self.name = 'FakeAv'
|
||||||
|
def b_setHp(*args):
|
||||||
|
pass
|
||||||
|
def b_setMojo(*args):
|
||||||
|
pass
|
||||||
|
def toonUp(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def magicWord(mw, av=None, zoneId=0, senderId=0):
|
||||||
|
if av is None:
|
||||||
|
av = FakeAv(senderId)
|
||||||
|
simbase.air.magicWordManager.doMagicWord(mw, av, zoneId, senderId)
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
builtins.magicWord = magicWord
|
||||||
|
|
@ -15,7 +15,7 @@ from . import ToontownMagicWordManagerAI
|
||||||
from toontown.tutorial import TutorialManagerAI
|
from toontown.tutorial import TutorialManagerAI
|
||||||
from toontown.catalog import CatalogManagerAI
|
from toontown.catalog import CatalogManagerAI
|
||||||
from otp.ai import TimeManagerAI
|
from otp.ai import TimeManagerAI
|
||||||
import WelcomeValleyManagerAI
|
from . import WelcomeValleyManagerAI
|
||||||
from toontown.building import DistributedBuildingMgrAI
|
from toontown.building import DistributedBuildingMgrAI
|
||||||
from toontown.building import DistributedTrophyMgrAI
|
from toontown.building import DistributedTrophyMgrAI
|
||||||
from toontown.estate import DistributedBankMgrAI
|
from toontown.estate import DistributedBankMgrAI
|
||||||
|
|
@ -39,7 +39,7 @@ from toontown.coghq import FactoryManagerAI
|
||||||
from toontown.coghq import MintManagerAI
|
from toontown.coghq import MintManagerAI
|
||||||
from toontown.coghq import LawOfficeManagerAI
|
from toontown.coghq import LawOfficeManagerAI
|
||||||
from toontown.coghq import CountryClubManagerAI
|
from toontown.coghq import CountryClubManagerAI
|
||||||
import NewsManagerAI
|
from . import NewsManagerAI
|
||||||
from toontown.hood import ZoneUtil
|
from toontown.hood import ZoneUtil
|
||||||
from toontown.fishing import DistributedFishingPondAI
|
from toontown.fishing import DistributedFishingPondAI
|
||||||
from toontown.safezone import DistributedFishingSpotAI
|
from toontown.safezone import DistributedFishingSpotAI
|
||||||
|
|
@ -51,7 +51,7 @@ from toontown.toon import NPCToons
|
||||||
from toontown.safezone import ButterflyGlobals
|
from toontown.safezone import ButterflyGlobals
|
||||||
from toontown.estate import EstateManagerAI
|
from toontown.estate import EstateManagerAI
|
||||||
from toontown.suit import SuitInvasionManagerAI
|
from toontown.suit import SuitInvasionManagerAI
|
||||||
import HolidayManagerAI
|
from . import HolidayManagerAI
|
||||||
from toontown.effects import FireworkManagerAI
|
from toontown.effects import FireworkManagerAI
|
||||||
from toontown.coghq import CogSuitManagerAI
|
from toontown.coghq import CogSuitManagerAI
|
||||||
from toontown.coghq import PromotionManagerAI
|
from toontown.coghq import PromotionManagerAI
|
||||||
|
|
@ -73,10 +73,10 @@ import string
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import DatabaseObject
|
from . import DatabaseObject
|
||||||
from direct.distributed.PyDatagram import PyDatagram
|
from direct.distributed.PyDatagram import PyDatagram
|
||||||
from direct.distributed.PyDatagramIterator import PyDatagramIterator
|
from direct.distributed.PyDatagramIterator import PyDatagramIterator
|
||||||
from ToontownAIMsgTypes import *
|
from .ToontownAIMsgTypes import *
|
||||||
from otp.otpbase.OTPGlobals import *
|
from otp.otpbase.OTPGlobals import *
|
||||||
from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI
|
from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI
|
||||||
#from otp.distributed.DistributedDirectoryAI import DistributedDirectoryAI
|
#from otp.distributed.DistributedDirectoryAI import DistributedDirectoryAI
|
||||||
|
|
@ -89,7 +89,7 @@ from toontown.parties import ToontownTimeManager
|
||||||
from toontown.coderedemption.TTCodeRedemptionMgrAI import TTCodeRedemptionMgrAI
|
from toontown.coderedemption.TTCodeRedemptionMgrAI import TTCodeRedemptionMgrAI
|
||||||
from toontown.distributed.NonRepeatableRandomSourceAI import NonRepeatableRandomSourceAI
|
from toontown.distributed.NonRepeatableRandomSourceAI import NonRepeatableRandomSourceAI
|
||||||
|
|
||||||
import ToontownGroupManager
|
from . import ToontownGroupManager
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
import pdb
|
import pdb
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
from toontown.toonbase.ToontownGlobals import *
|
||||||
|
from otp.otpbase import OTPGlobals
|
||||||
|
|
||||||
|
# these are array indexs
|
||||||
|
# since there are no structs
|
||||||
|
GROUPMEMBER = 0
|
||||||
|
GROUPINVITE = 1
|
||||||
|
|
||||||
|
class ToontownGroupManager:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.groupLists = []
|
||||||
|
self.avIdDict = {}
|
||||||
|
#group is [[members],[invitees]]
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.groupLists = []
|
||||||
|
self.avIdDict = {}
|
||||||
|
|
||||||
|
def inviteToGroup(self, memberId, joinerId):
|
||||||
|
#print("Group Manager Invite to group")
|
||||||
|
group = self.getGroup(memberId)
|
||||||
|
joinerGroup = self.getGroup(joinerId)
|
||||||
|
if group and ((joinerId in group[GROUPMEMBER]) or (joinerId in group[GROUPINVITE])):
|
||||||
|
#joiner is already in the group
|
||||||
|
#print ("joiner in same group")
|
||||||
|
return None
|
||||||
|
elif joinerGroup and len(joinerGroup) > 1:
|
||||||
|
#joiner is in another group
|
||||||
|
#print ("joiner in another group")
|
||||||
|
return None
|
||||||
|
if group:
|
||||||
|
# lookup the member's group and add the joiner
|
||||||
|
group[GROUPINVITE].append(joinerId)
|
||||||
|
#self.avIdDict[joiner] = group
|
||||||
|
#print ("added joiner to group")
|
||||||
|
else:
|
||||||
|
# there is no group so add a new one
|
||||||
|
newGroupList = [[memberId],[joinerId]]
|
||||||
|
self.avIdDict[memberId] = newGroupList
|
||||||
|
self.groupLists.append(newGroupList)
|
||||||
|
group = newGroupList
|
||||||
|
#print ("creating new group")
|
||||||
|
#tell each group member that the joiner has been invited
|
||||||
|
for avId in group[GROUPMEMBER]:
|
||||||
|
if avId != joinerId:
|
||||||
|
avatar = simbase.air.doId2do.get(avId)
|
||||||
|
if avatar:
|
||||||
|
avatar.sendUpdate("receiveInvitePosted", [joinerId])
|
||||||
|
|
||||||
|
#send the invite message to joinerId
|
||||||
|
avatar = simbase.air.doId2do.get(joinerId)
|
||||||
|
if avatar:
|
||||||
|
avatar.sendUpdate("receiveGroupInvite", [group[GROUPMEMBER]])
|
||||||
|
return group
|
||||||
|
|
||||||
|
def useInviteToJoin(self, memberIdList, joinerId):
|
||||||
|
memberId = memberIdList[0]
|
||||||
|
joinerGroup = self.getGroup(joinerId)
|
||||||
|
if joinerGroup:
|
||||||
|
if len(joinerGroup) > 1:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# if the joiner is in a group by themselves, remove that group
|
||||||
|
self.removeFromGroup(joinerId)
|
||||||
|
|
||||||
|
group = self.getGroup(memberId)
|
||||||
|
if group and (joinerId in group[GROUPINVITE]):
|
||||||
|
group[GROUPINVITE].remove(joinerId)
|
||||||
|
group[GROUPMEMBER].append(joinerId)
|
||||||
|
self.avIdDict[joinerId] = group
|
||||||
|
|
||||||
|
for avId in group[GROUPMEMBER]:
|
||||||
|
avatar = simbase.air.doId2do.get(avId)
|
||||||
|
if avatar:
|
||||||
|
if avId != joinerId:
|
||||||
|
avatar.sendUpdate("receiveJoinGroup", [[joinerId]])
|
||||||
|
else:
|
||||||
|
avatar.sendUpdate("receiveJoinGroup", [group[GROUPMEMBER]])
|
||||||
|
else:
|
||||||
|
#TODO tell the joiner that the group invitee was invalid
|
||||||
|
return
|
||||||
|
|
||||||
|
def removeInvitation(self, memberId, joinerId):
|
||||||
|
group = self.getGroup(memberId)
|
||||||
|
if group:
|
||||||
|
#tell each group member that the invitation has been retracted
|
||||||
|
for avId in group[GROUPMEMBER]:
|
||||||
|
if avId != joinerId:
|
||||||
|
avatar = simbase.air.doId2do.get(avId)
|
||||||
|
if avatar:
|
||||||
|
avatar.sendUpdate("receiveInviteRemoved", [joinerId])
|
||||||
|
|
||||||
|
#tell the joiner that the invitation has been retracted
|
||||||
|
avatar = simbase.air.doId2do.get(joinerId)
|
||||||
|
if avatar:
|
||||||
|
avatar.sendUpdate("receiveGroupInviteRetract", [memberId])
|
||||||
|
|
||||||
|
|
||||||
|
if (joinerId in group[GROUPMEMBER]):
|
||||||
|
#joiner is already in the group do nothing
|
||||||
|
pass
|
||||||
|
elif (joinerId in group[GROUPINVITE]):
|
||||||
|
group[GROUPINVITE].remove(joinerId)
|
||||||
|
else:
|
||||||
|
#there was no invitation
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def removeFromGroup(self, leaverId):
|
||||||
|
print("removeFromGroup")
|
||||||
|
group = self.getGroup(leaverId)
|
||||||
|
if group and (leaverId in group[GROUPMEMBER]):
|
||||||
|
print("Group found for %s" % (leaverId))
|
||||||
|
#send everyone in the group a message memberId has left
|
||||||
|
for avId in group[GROUPMEMBER]:
|
||||||
|
if avId != leaverId:
|
||||||
|
avatar = simbase.air.doId2do.get(avId)
|
||||||
|
if avatar:
|
||||||
|
avatar.sendUpdate("receiveLeaveGroup", [[leaverId]])
|
||||||
|
else:
|
||||||
|
avatar = simbase.air.doId2do.get(avId)
|
||||||
|
if avatar:
|
||||||
|
avatar.sendUpdate("receiveLeaveGroup", [group[GROUPMEMBER]])
|
||||||
|
#remove the memberId from the groupList
|
||||||
|
group[GROUPMEMBER].remove(leaverId)
|
||||||
|
else:
|
||||||
|
#print("No group found for %s" % (leaverId))
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# if the group is empty remove it
|
||||||
|
if group and len(group[GROUPMEMBER]) <= 1:
|
||||||
|
self.groupLists.remove(group)
|
||||||
|
for member in group[GROUPMEMBER]:
|
||||||
|
if self.avIdDict.has_key(member):
|
||||||
|
self.avIdDict.pop(member)
|
||||||
|
# clear the member's group affiliation
|
||||||
|
if self.avIdDict.has_key(leaverId):
|
||||||
|
self.avIdDict.pop(leaverId)
|
||||||
|
|
||||||
|
def getGroup(self, memberId):
|
||||||
|
if self.avIdDict.has_key(memberId):
|
||||||
|
return self.avIdDict[memberId]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 self.fireworkShows.has_key(zone):
|
||||||
|
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 not self.fireworkShows.has_key(zone):
|
||||||
|
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 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 self.fireworkShows.has_key(zone)
|
||||||
|
|
||||||
|
|
@ -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 direct.showbase 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', "")
|
||||||
|
|
||||||
|
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 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 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 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 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 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 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 self.doId2do.has_key(doId):
|
||||||
|
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 self.zoneId2do.has_key(zoneId):
|
||||||
|
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, 'w')
|
||||||
|
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', 'r')
|
||||||
|
if os.path.exists(fileName):
|
||||||
|
os.remove(fileName)
|
||||||
|
except IOError:
|
||||||
|
try:
|
||||||
|
file = open(fileName)
|
||||||
|
except IOError:
|
||||||
|
# Default Jackpot Amount
|
||||||
|
return self.DefaultReward
|
||||||
|
|
||||||
|
file.seek(0)
|
||||||
|
jackpots = self.loadFrom(file)
|
||||||
|
file.close()
|
||||||
|
return jackpots
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# Method: getSuperJackpot
|
||||||
|
# Purpose: This method returns the super jackpot amount for
|
||||||
|
# the specified zone. It calculates which hood
|
||||||
|
# the zone is in and returns the shared jackpot
|
||||||
|
# amount for that hood.
|
||||||
|
# Input: zoneId - retrieve jackpot for this zone's hood
|
||||||
|
# Output: returns jackpot for hood that zoneid is found in
|
||||||
|
############################################################
|
||||||
|
def getSuperJackpot(self, zoneId):
|
||||||
|
hood = self.__hoodToUse(zoneId)
|
||||||
|
self.notify.info('getSuperJackpot: hoodJackpots %s \t hood %s' % (self.__hoodJackpots, hood))
|
||||||
|
return self.__hoodJackpots.get(hood, [BG.MIN_SUPER_JACKPOT])[0]
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# Method: __startCloseEvent
|
||||||
|
# Purpose: This method starts to close Bingo Night down. One
|
||||||
|
# more super card game will be played at the end
|
||||||
|
# of the hour(unless the times are changed).
|
||||||
|
# Input: None
|
||||||
|
# Output: None
|
||||||
|
############################################################
|
||||||
|
def __startCloseEvent(self):
|
||||||
|
self.finalGame = BG.CLOSE_EVENT
|
||||||
|
|
||||||
|
if self.waitTaskName == 'waitForIntermission':
|
||||||
|
taskMgr.remove(self.waitTaskName)
|
||||||
|
self.__startIntermission()
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# Method: handleSuperBingoWin
|
||||||
|
# Purpose: This method handles a victory when a super
|
||||||
|
# bingo game has been one. It updates the jackpot
|
||||||
|
# amount and tells each of the other ponds in that
|
||||||
|
# hood that they did not win.
|
||||||
|
# Input: zoneId - pond who won the bingo game.
|
||||||
|
# Output: None
|
||||||
|
############################################################
|
||||||
|
def handleSuperBingoWin(self, zoneId):
|
||||||
|
# Reset the Jackpot and unmark the dirty bit.
|
||||||
|
|
||||||
|
hood = self.__hoodToUse(zoneId)
|
||||||
|
self.__hoodJackpots[hood][0] = self.DefaultReward[hood][0]
|
||||||
|
self.__hoodJackpots[hood][1] = 0
|
||||||
|
|
||||||
|
# tell everyone who won
|
||||||
|
#simbase.air.newsManager.bingoWin(zoneId)
|
||||||
|
|
||||||
|
# Tell the other ponds that they did not win and should handle the loss
|
||||||
|
for doId in self.hood2doIdList[hood]:
|
||||||
|
distObj = self.doId2do[doId]
|
||||||
|
if distObj.zoneId != zoneId:
|
||||||
|
self.notify.info("handleSuperBingoWin: Did not win in zone %s" %(distObj.zoneId))
|
||||||
|
distObj.handleSuperBingoLoss()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
from otp.ai.AIBaseGlobal import *
|
||||||
|
from direct.task import Task
|
||||||
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
|
from toontown.quest import Quests
|
||||||
|
from toontown.toon import NPCToons
|
||||||
|
import random
|
||||||
|
from direct.showbase import PythonUtil
|
||||||
|
from . import FishGlobals
|
||||||
|
from toontown.toonbase import TTLocalizer
|
||||||
|
from . import FishBase
|
||||||
|
from . import FishGlobals
|
||||||
|
from toontown.hood import ZoneUtil
|
||||||
|
from toontown.toonbase import ToontownGlobals
|
||||||
|
|
||||||
|
|
||||||
|
class FishManagerAI:
|
||||||
|
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory("FishManagerAI")
|
||||||
|
|
||||||
|
def __init__(self, air):
|
||||||
|
self.air = air
|
||||||
|
|
||||||
|
def __chooseItem(self, av, zoneId):
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
rand = random.random() * 100.0
|
||||||
|
for cutoff in FishGlobals.SortedProbabilityCutoffs:
|
||||||
|
if rand <= cutoff:
|
||||||
|
itemType = FishGlobals.ProbabilityDict[cutoff]
|
||||||
|
self.notify.debug("__chooseItem: %s" % (itemType))
|
||||||
|
return itemType
|
||||||
|
self.notify.warning("Somehow we did not choose an item, returning boot")
|
||||||
|
return FishGlobals.BootItem
|
||||||
|
|
||||||
|
def __chooseFish(self, av, zoneId):
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
branchZone = ZoneUtil.getCanonicalBranchZone(zoneId)
|
||||||
|
success, genus, species, weight = FishGlobals.getRandomFishVitals(branchZone, rodId)
|
||||||
|
return (success, genus, species, weight)
|
||||||
|
|
||||||
|
def recordCatch(self, avId, zoneId, pondZoneId):
|
||||||
|
# Chooses an item to find in the water. Returns a (code,
|
||||||
|
# item) pair where code indicates the type of item, and item
|
||||||
|
# is the particular item id.
|
||||||
|
av = self.air.doId2do.get(avId)
|
||||||
|
if not av:
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
#check to see if bingo cheating is turned on
|
||||||
|
if av.bingoCheat:
|
||||||
|
# Fished up a boot
|
||||||
|
self.notify.debug("av: %s caught the boot" % (avId))
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
self.air.writeServerEvent("fishedBoot", avId, "%s|%s" % (rodId, zoneId))
|
||||||
|
self.air.handleAvCatch(avId, pondZoneId, FishGlobals.BingoBoot)
|
||||||
|
return (FishGlobals.BootItem, None)
|
||||||
|
|
||||||
|
# First, check for a quest item.
|
||||||
|
item = self.air.questManager.findItemInWater(av, zoneId)
|
||||||
|
if item:
|
||||||
|
self.notify.debug("av: %s caught quest item: %s" % (avId, item))
|
||||||
|
# Write to server logs
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
self.air.writeServerEvent("fishedQuestItem", avId, "%s|%s|%s" % (rodId, zoneId, item))
|
||||||
|
return (FishGlobals.QuestItem, item)
|
||||||
|
|
||||||
|
# Ok, no quest item, now let's see what you pull out
|
||||||
|
# Could be a fish, jellybeans, a boot, shirts, etc
|
||||||
|
itemType = self.__chooseItem(av, zoneId)
|
||||||
|
|
||||||
|
if itemType == FishGlobals.FishItem:
|
||||||
|
# Choose which fish (this may come back with the boot too)
|
||||||
|
success, genus, species, weight = self.__chooseFish(av, zoneId)
|
||||||
|
if success:
|
||||||
|
# Ok, you found a fish
|
||||||
|
self.air.handleAvCatch(avId, pondZoneId, (genus, species))
|
||||||
|
fish = FishBase.FishBase(genus, species, weight)
|
||||||
|
# do you already have one like it?
|
||||||
|
inTank = av.fishTank.hasFish(genus, species)
|
||||||
|
if inTank:
|
||||||
|
# TODO: This is a bit wasteful to loop through the fish tank twice
|
||||||
|
hasBiggerAlready = av.fishTank.hasBiggerFish(genus, species, weight)
|
||||||
|
else:
|
||||||
|
# If we already know it is not in the tank, no sense searching around
|
||||||
|
# to see if we have one bigger
|
||||||
|
hasBiggerAlready = 0
|
||||||
|
added = av.addFishToTank(fish)
|
||||||
|
if added:
|
||||||
|
self.notify.debug("av: %s caught fish: %s %s %s" %
|
||||||
|
(avId, genus, species, weight))
|
||||||
|
# See what the collect result will be
|
||||||
|
# NOTE: this does not actually collect the fish. The NPC
|
||||||
|
# fisherman does that work
|
||||||
|
collectResult = av.fishCollection.getCollectResult(fish)
|
||||||
|
|
||||||
|
# Catch a fish, get a heal
|
||||||
|
# Nope - changed my mind. Fishing does not heal anymore
|
||||||
|
# It is strange to be able to heal out on the streets, and while
|
||||||
|
# you are in the playground you heal anyways
|
||||||
|
# av.toonUp(FishGlobals.HealAmount)
|
||||||
|
|
||||||
|
# Write to server logs
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
self.air.writeServerEvent("fishedFish", avId, "%s|%s|%s|%s|%s|%s" %
|
||||||
|
(rodId, zoneId, genus, species, weight, fish.getValue()))
|
||||||
|
if collectResult == FishGlobals.COLLECT_NO_UPDATE:
|
||||||
|
return (FishGlobals.FishItem, fish)
|
||||||
|
elif collectResult == FishGlobals.COLLECT_NEW_ENTRY:
|
||||||
|
# If it is not in our tank also, it really is a new entry
|
||||||
|
if not inTank:
|
||||||
|
return (FishGlobals.FishItemNewEntry, fish)
|
||||||
|
# Ok, we already have one in our tank. If we do not already
|
||||||
|
# have a bigger one it is a new record
|
||||||
|
elif not hasBiggerAlready:
|
||||||
|
return (FishGlobals.FishItemNewRecord, fish)
|
||||||
|
# Otherwise, just a normal catch
|
||||||
|
else:
|
||||||
|
return (FishGlobals.FishItem, fish)
|
||||||
|
elif collectResult == FishGlobals.COLLECT_NEW_RECORD:
|
||||||
|
# If we have one of these in our tank, check to see if
|
||||||
|
# it is bigger. If the one we already have in our tank is bigger
|
||||||
|
# then we do not get a new record set.
|
||||||
|
if hasBiggerAlready:
|
||||||
|
# No new record, we already have this fish
|
||||||
|
# beat, but it is still in our tank - we have
|
||||||
|
# not sold it yet so it is not in our
|
||||||
|
# collection.
|
||||||
|
return (FishGlobals.FishItem, fish)
|
||||||
|
else:
|
||||||
|
return (FishGlobals.FishItemNewRecord, fish)
|
||||||
|
else:
|
||||||
|
self.notify.error("unrecognized collectResult: %s" % (collectResult))
|
||||||
|
else:
|
||||||
|
self.notify.debug("av: %s is over the tank limit" % (avId))
|
||||||
|
return (FishGlobals.OverTankLimit, None)
|
||||||
|
else:
|
||||||
|
# If you did not choose a fish, you get the boot
|
||||||
|
self.notify.debug("av: %s tried to catch fish, but got the boot" % (avId))
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
self.air.writeServerEvent("fishedBoot", avId, "%s|%s" % (rodId, zoneId))
|
||||||
|
self.air.handleAvCatch(avId, pondZoneId, FishGlobals.BingoBoot)
|
||||||
|
return (FishGlobals.BootItem, None)
|
||||||
|
elif itemType == FishGlobals.BootItem:
|
||||||
|
# Fished up a boot
|
||||||
|
self.notify.debug("av: %s caught the boot" % (avId))
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
self.air.writeServerEvent("fishedBoot", avId, "%s|%s" % (rodId, zoneId))
|
||||||
|
self.air.handleAvCatch(avId, pondZoneId, FishGlobals.BingoBoot)
|
||||||
|
return (FishGlobals.BootItem, None)
|
||||||
|
elif itemType == FishGlobals.JellybeanItem:
|
||||||
|
# Fished up some jellybeans
|
||||||
|
rodId = av.getFishingRod()
|
||||||
|
jellybeanAmount = FishGlobals.Rod2JellybeanDict[rodId]
|
||||||
|
av.addMoney(jellybeanAmount)
|
||||||
|
self.notify.debug("av: %s caught %s jellybeans" % (avId, jellybeanAmount))
|
||||||
|
self.air.writeServerEvent("fishedJellybeans", avId, "%s|%s|%s" % (rodId, zoneId, jellybeanAmount))
|
||||||
|
return (FishGlobals.JellybeanItem, jellybeanAmount)
|
||||||
|
|
||||||
|
def creditFishTank(self, av):
|
||||||
|
"""
|
||||||
|
Do all the work of selling the tank and updating the collection.
|
||||||
|
Also updates your trophy status and maxHP if needed.
|
||||||
|
Returns 1 if you earned a trophy, 0 if you did not.
|
||||||
|
"""
|
||||||
|
assert(self.notify.debug("creditFishTank av: %s is selling all fish" % (av.getDoId())))
|
||||||
|
oldBonus = int(len(av.fishCollection)/FishGlobals.FISH_PER_BONUS)
|
||||||
|
|
||||||
|
# give the avatar jellybeans in exchange for his fish
|
||||||
|
value = av.fishTank.getTotalValue()
|
||||||
|
av.addMoney(value)
|
||||||
|
|
||||||
|
# update the avatar collection for each fish
|
||||||
|
for fish in av.fishTank.fishList:
|
||||||
|
av.fishCollection.collectFish(fish)
|
||||||
|
|
||||||
|
# clear out the fishTank
|
||||||
|
av.b_setFishTank([],[],[])
|
||||||
|
|
||||||
|
# update the collection in the database
|
||||||
|
av.d_setFishCollection(*av.fishCollection.getNetLists())
|
||||||
|
|
||||||
|
newBonus = int(len(av.fishCollection)/FishGlobals.FISH_PER_BONUS)
|
||||||
|
if newBonus > oldBonus:
|
||||||
|
self.notify.info("avatar %s gets a bonus: old: %s, new: %s" % (av.doId, oldBonus, newBonus))
|
||||||
|
oldMaxHp = av.getMaxHp()
|
||||||
|
newMaxHp = min(ToontownGlobals.MaxHpLimit, oldMaxHp + newBonus - oldBonus)
|
||||||
|
av.b_setMaxHp(newMaxHp)
|
||||||
|
# Also, give them a full heal
|
||||||
|
av.toonUp(newMaxHp)
|
||||||
|
# update the av's trophy list
|
||||||
|
newTrophies = av.getFishingTrophies()
|
||||||
|
trophyId = len(newTrophies)
|
||||||
|
newTrophies.append(trophyId)
|
||||||
|
av.b_setFishingTrophies(newTrophies)
|
||||||
|
self.air.writeServerEvent("fishTrophy", av.doId, "%s" % (trophyId))
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
assert(self.notify.debug("avatar %s no bonus: old: %s, new: %s" % (av.doId, oldBonus, newBonus)))
|
||||||
|
return 0
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
from toontown.toonbase import TTLocalizer
|
||||||
|
from direct.distributed.ClockDelta import *
|
||||||
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
|
from . import DistributedNPCToonBaseAI
|
||||||
|
from direct.task import Task
|
||||||
|
import random
|
||||||
|
|
||||||
|
class NPCDialogue:
|
||||||
|
"""
|
||||||
|
The NPC dialogue for a given topic and set of participants
|
||||||
|
"""
|
||||||
|
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory("NPCDialogue")
|
||||||
|
|
||||||
|
def __init__(self, participant, dialogueTopic):
|
||||||
|
self.participants = {}
|
||||||
|
|
||||||
|
if dialogueTopic in TTLocalizer.toontownDialogues:
|
||||||
|
self.topic = dialogueTopic
|
||||||
|
else:
|
||||||
|
self.notify.warning("Dialogue does not exist: %s" %dialogueTopic)
|
||||||
|
self.topic = TTLocalizer.BoringTopic
|
||||||
|
|
||||||
|
self.conversation = TTLocalizer.toontownDialogues[self.topic]
|
||||||
|
|
||||||
|
if participant and isinstance(participant, DistributedNPCToonBaseAI.DistributedNPCToonBaseAI):
|
||||||
|
self.addParticipant(participant)
|
||||||
|
self.participantProgress = 0
|
||||||
|
self.currentParticipant = participant.npcId
|
||||||
|
else:
|
||||||
|
self.notify.warning("Participant does not exist: %s" %participant)
|
||||||
|
self.participantProgress = 0
|
||||||
|
self.currrentParticipant = None
|
||||||
|
|
||||||
|
def calcMaxNumMsgs(self):
|
||||||
|
"""
|
||||||
|
Find the participant that has the most number of things to say
|
||||||
|
"""
|
||||||
|
self.maxNumMsgs = 0
|
||||||
|
for participant, spiel in self.conversation.items():
|
||||||
|
if len(spiel)>self.maxNumMsgs and participant[1] in self.participants:
|
||||||
|
self.maxNumMsgs = len(spiel)
|
||||||
|
|
||||||
|
def getTopic(self):
|
||||||
|
"""
|
||||||
|
Accessor function for topic
|
||||||
|
"""
|
||||||
|
return self.topic
|
||||||
|
|
||||||
|
def addParticipant(self, participant):
|
||||||
|
"""
|
||||||
|
Add a new participant
|
||||||
|
"""
|
||||||
|
if self.getNumParticipants() > self.getMaxParticipants():
|
||||||
|
return False
|
||||||
|
if participant:
|
||||||
|
for partPos in self.conversation.keys():
|
||||||
|
if partPos[1] == participant.npcId:
|
||||||
|
if not (participant.npcId in self.participants):
|
||||||
|
self.participants[participant.npcId] = [participant]
|
||||||
|
else:
|
||||||
|
if participant not in self.participants[participant.npcId]:
|
||||||
|
self.participants[participant.npcId].append(participant)
|
||||||
|
else:
|
||||||
|
self.notify.warning("Participant: %s already in the conversation" %participant)
|
||||||
|
self.calcMaxNumMsgs()
|
||||||
|
return True
|
||||||
|
self.notify.warning("Participant: %s should not be in conversation" %participant)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def removeParticipant(self, participant):
|
||||||
|
"""
|
||||||
|
Remove a participant
|
||||||
|
"""
|
||||||
|
if participant.npcId in self.participants:
|
||||||
|
if participant.npcId == self.currentParticipant:
|
||||||
|
self.getNextParticipant()
|
||||||
|
self.participants[participant.npcId].remove(participant)
|
||||||
|
if self.participants[participant.npcId] == []:
|
||||||
|
del self.participants[participant.npcId]
|
||||||
|
self.calcMaxNumMsgs()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getNextParticipant(self):
|
||||||
|
while 1:
|
||||||
|
self.currentParticipant = self.calcNextParticipant()
|
||||||
|
if self.currentParticipant in self.participants:
|
||||||
|
break
|
||||||
|
|
||||||
|
def calcNextParticipant(self):
|
||||||
|
"""
|
||||||
|
Returns the next in line to talk
|
||||||
|
"""
|
||||||
|
nextParticipant = None
|
||||||
|
|
||||||
|
for partPos in self.conversation.keys():
|
||||||
|
if partPos[1] == self.currentParticipant:
|
||||||
|
nextPos = partPos[0]+1
|
||||||
|
break
|
||||||
|
if nextPos>len(self.conversation):
|
||||||
|
nextPos = 1
|
||||||
|
self.participantProgress = (self.participantProgress+1)%self.maxNumMsgs
|
||||||
|
for partPos in self.conversation.keys():
|
||||||
|
if partPos[0] == nextPos:
|
||||||
|
nextParticipant = partPos[1]
|
||||||
|
return nextParticipant
|
||||||
|
|
||||||
|
return self.currentParticipant
|
||||||
|
|
||||||
|
|
||||||
|
def getMaxParticipants(self):
|
||||||
|
"""
|
||||||
|
Number of conversation pieces provided in TTLocalizer
|
||||||
|
"""
|
||||||
|
return len(self.conversation)
|
||||||
|
|
||||||
|
def getNumParticipants(self):
|
||||||
|
"""
|
||||||
|
Returns the number of NPC's currently participating
|
||||||
|
"""
|
||||||
|
return len(self.participants)
|
||||||
|
|
||||||
|
def isRunning(self):
|
||||||
|
if taskMgr.hasTaskNamed("Dialogue"+self.topic):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Start up a dialogue amongst the participants
|
||||||
|
"""
|
||||||
|
self.nextChatTime = 0
|
||||||
|
|
||||||
|
taskMgr.add(self.__blather, "Dialogue"+self.topic)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
This conversation is over!
|
||||||
|
"""
|
||||||
|
taskMgr.remove("Dialogue"+self.topic)
|
||||||
|
|
||||||
|
def __blather(self, task):
|
||||||
|
"""
|
||||||
|
Speak in turn
|
||||||
|
"""
|
||||||
|
now = globalClock.getFrameTime()
|
||||||
|
if now < self.nextChatTime:
|
||||||
|
return Task.cont
|
||||||
|
|
||||||
|
if not self.currentParticipant:
|
||||||
|
return Task.done
|
||||||
|
|
||||||
|
# Increment the participantProgress
|
||||||
|
for partPos in self.conversation.keys():
|
||||||
|
if partPos[1] == self.currentParticipant:
|
||||||
|
convKey = partPos
|
||||||
|
break
|
||||||
|
if self.participantProgress >= len(self.conversation[convKey]):
|
||||||
|
self.getNextParticipant()
|
||||||
|
return Task.cont
|
||||||
|
|
||||||
|
# Select the current spiel
|
||||||
|
#msg = self.conversation[self.participants[self.currentParticipant]][self.participantProgress]
|
||||||
|
|
||||||
|
for participant in self.participants[self.currentParticipant]:
|
||||||
|
chatFlags = CFSpeech | CFTimeout
|
||||||
|
|
||||||
|
participant.sendUpdate("setChat", [self.topic, convKey[0], convKey[1], self.participantProgress, chatFlags])
|
||||||
|
|
||||||
|
self.getNextParticipant()
|
||||||
|
|
||||||
|
# Delay before next message
|
||||||
|
self.nextChatTime = now + 5.0
|
||||||
|
|
||||||
|
return Task.cont
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
from toontown.toonbase import TTLocalizer
|
||||||
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
|
from . import NPCDialogue
|
||||||
|
|
||||||
|
class NPCDialogueManagerAI:
|
||||||
|
"""
|
||||||
|
Create and distroy dialogues here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory("NPCDialogueManagerAI")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.dialogues = []
|
||||||
|
|
||||||
|
def createNewDialogue(self, participant, dialogueTopic):
|
||||||
|
"""
|
||||||
|
Create a new dialogue
|
||||||
|
"""
|
||||||
|
dialogue = NPCDialogue.NPCDialogue(participant, dialogueTopic)
|
||||||
|
result = dialogue.start()
|
||||||
|
if result:
|
||||||
|
self.dialogues.append(dialogue)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def requestDialogue(self, participant, dialogueTopic):
|
||||||
|
"""
|
||||||
|
Request to be added to the dialogue: dialogueTopic
|
||||||
|
"""
|
||||||
|
for dialogue in self.dialogues:
|
||||||
|
if dialogue.getTopic() == dialogueTopic:
|
||||||
|
result = dialogue.addParticipant(participant)
|
||||||
|
if result and not dialogue.isRunning():
|
||||||
|
result = dialogue.start()
|
||||||
|
return result
|
||||||
|
|
||||||
|
result = self.createNewDialogue(participant, dialogueTopic)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def leaveDialogue(self, participant, dialogueTopic):
|
||||||
|
"""
|
||||||
|
Stop participating in this dialogue
|
||||||
|
"""
|
||||||
|
result = False
|
||||||
|
for dialogue in self.dialogues:
|
||||||
|
if dialogue.getTopic() == dialogueTopic:
|
||||||
|
result = dialogue.removeParticipant(participant)
|
||||||
|
|
||||||
|
if dialogue.getNumParticipants() == 0:
|
||||||
|
dialogue.stop()
|
||||||
|
try:
|
||||||
|
self.dialogues.remove(dialogue)
|
||||||
|
except:
|
||||||
|
self.notify.warning("Couldn't find the dialogue: %s" %dialogue)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
@ -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(zip(self.__queryTypesDict.values(),
|
||||||
|
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 self.__queryTypesDict.keys()
|
||||||
|
|
||||||
|
def getQueryTypeString(self,qId):
|
||||||
|
return self.__queryStringDict.get(qId,None)
|
||||||
|
|
||||||
|
def sendQuery(self,queryTypeString,queryData):
|
||||||
|
"""
|
||||||
|
Sends a query to the data store. The format of the query is
|
||||||
|
defined in the store's class definition.
|
||||||
|
"""
|
||||||
|
if self.__enabled:
|
||||||
|
qId = self.__queryTypesDict.get(queryTypeString,None)
|
||||||
|
if qId is not None:
|
||||||
|
query = (qId,queryData)
|
||||||
|
# pack the data to be sent to the Uberdog store.
|
||||||
|
pQuery = pickle.dumps(query)
|
||||||
|
self.__storeMgr.queryStore(self.__storeId,pQuery)
|
||||||
|
else:
|
||||||
|
self.notify.debug('Tried to send invalid query type: \'%s\'' % (queryTypeString,))
|
||||||
|
else:
|
||||||
|
self.notify.warning('Client currently stopped. \'%s\' query will fail.' % (queryTypeString,))
|
||||||
|
|
||||||
|
def receiveResults(self,data):
|
||||||
|
"""
|
||||||
|
Upon receiving a query, the store will respond with a result.
|
||||||
|
This function will call the resultsCallback function with the
|
||||||
|
result data as its sole argument. Try to treat the
|
||||||
|
resultsCallback function as an event that is fired whenever
|
||||||
|
the client receives data from the store.
|
||||||
|
"""
|
||||||
|
# unpack the results from the Uberdog store.
|
||||||
|
|
||||||
|
if data == 'Store not found':
|
||||||
|
self.notify.debug('%s not present on uberdog. Query dropped.' %(self.__storeClass.__name__,))
|
||||||
|
else:
|
||||||
|
results = pickle.loads(data)
|
||||||
|
self.__resultsCallback(results)
|
||||||
|
|
||||||
|
def __startClient(self):
|
||||||
|
"""
|
||||||
|
Allow the client to send queries and receive results from its
|
||||||
|
associated data store.
|
||||||
|
"""
|
||||||
|
self.accept('TDS-results-%d'%self.__storeId,self.receiveResults)
|
||||||
|
self.__enabled = True
|
||||||
|
|
||||||
|
def __stopClient(self):
|
||||||
|
"""
|
||||||
|
Disallow the client from sending queries and receiving results
|
||||||
|
from its associated data store.
|
||||||
|
"""
|
||||||
|
self.ignoreAll()
|
||||||
|
self.__enabled = False
|
||||||
|
|
||||||
|
def deleteBackupStores(self):
|
||||||
|
"""
|
||||||
|
Delete any backed up stores from previous year's
|
||||||
|
"""
|
||||||
|
if self.wantDsm:
|
||||||
|
self.__storeMgr.deleteBackupStores()
|
||||||
Loading…
Reference in New Issue