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.catalog import CatalogManagerAI
|
||||
from otp.ai import TimeManagerAI
|
||||
import WelcomeValleyManagerAI
|
||||
from . import WelcomeValleyManagerAI
|
||||
from toontown.building import DistributedBuildingMgrAI
|
||||
from toontown.building import DistributedTrophyMgrAI
|
||||
from toontown.estate import DistributedBankMgrAI
|
||||
|
|
@ -39,7 +39,7 @@ from toontown.coghq import FactoryManagerAI
|
|||
from toontown.coghq import MintManagerAI
|
||||
from toontown.coghq import LawOfficeManagerAI
|
||||
from toontown.coghq import CountryClubManagerAI
|
||||
import NewsManagerAI
|
||||
from . import NewsManagerAI
|
||||
from toontown.hood import ZoneUtil
|
||||
from toontown.fishing import DistributedFishingPondAI
|
||||
from toontown.safezone import DistributedFishingSpotAI
|
||||
|
|
@ -51,7 +51,7 @@ from toontown.toon import NPCToons
|
|||
from toontown.safezone import ButterflyGlobals
|
||||
from toontown.estate import EstateManagerAI
|
||||
from toontown.suit import SuitInvasionManagerAI
|
||||
import HolidayManagerAI
|
||||
from . import HolidayManagerAI
|
||||
from toontown.effects import FireworkManagerAI
|
||||
from toontown.coghq import CogSuitManagerAI
|
||||
from toontown.coghq import PromotionManagerAI
|
||||
|
|
@ -73,10 +73,10 @@ import string
|
|||
import os
|
||||
import time
|
||||
|
||||
import DatabaseObject
|
||||
from . import DatabaseObject
|
||||
from direct.distributed.PyDatagram import PyDatagram
|
||||
from direct.distributed.PyDatagramIterator import PyDatagramIterator
|
||||
from ToontownAIMsgTypes import *
|
||||
from .ToontownAIMsgTypes import *
|
||||
from otp.otpbase.OTPGlobals import *
|
||||
from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI
|
||||
#from otp.distributed.DistributedDirectoryAI import DistributedDirectoryAI
|
||||
|
|
@ -89,7 +89,7 @@ from toontown.parties import ToontownTimeManager
|
|||
from toontown.coderedemption.TTCodeRedemptionMgrAI import TTCodeRedemptionMgrAI
|
||||
from toontown.distributed.NonRepeatableRandomSourceAI import NonRepeatableRandomSourceAI
|
||||
|
||||
import ToontownGroupManager
|
||||
from . import ToontownGroupManager
|
||||
|
||||
if __debug__:
|
||||
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