252 lines
12 KiB
Python
252 lines
12 KiB
Python
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
|
|
from direct.distributed.PyDatagram import *
|
|
from direct.directnotify.DirectNotifyGlobal import directNotify
|
|
from direct.task import Task
|
|
from PartyGlobals import *
|
|
from datetime import datetime, timedelta
|
|
from panda3d.core import *
|
|
|
|
class GlobalPartyManagerUD(DistributedObjectGlobalUD):
|
|
notify = directNotify.newCategory('GlobalPartyManagerUD')
|
|
|
|
# This uberdog MUST be up before the AIs, as AIs talk to this UD
|
|
|
|
def announceGenerate(self):
|
|
DistributedObjectGlobalUD.announceGenerate(self)
|
|
self.notify.debug("GPMUD generated")
|
|
self.senders2Mgrs = {}
|
|
self.host2PartyId = {} # just a reference mapping
|
|
self.id2Party = {} # This should be replaced with a longterm datastore
|
|
self.party2PubInfo = {} # This should not be longterm
|
|
self.tempSlots = {}
|
|
PARTY_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
startTime = datetime.strptime('2014-01-20 11:50:00', PARTY_TIME_FORMAT)
|
|
endTime = datetime.strptime('2014-01-20 12:20:00', PARTY_TIME_FORMAT)
|
|
self.nextId = 0
|
|
#self.host2Party[100000001] = {'hostId': 100000001, 'start': startTime, 'end': endTime, 'partyId': 1717986918400000, 'decorations': [[3,5,7,6]], 'activities': [[10,13,6,18],[7,8,7,0]],'inviteTheme':1,'isPrivate':0,'inviteeIds':[]}
|
|
config = getConfigShowbase()
|
|
self.wantInstantParties = config.GetBool('want-instant-parties', 0)
|
|
|
|
# Setup tasks
|
|
self.runAtNextInterval()
|
|
|
|
# GPMUD -> PartyManagerAI messaging
|
|
def _makeAIMsg(self, field, values, recipient):
|
|
return self.air.dclassesByName['DistributedPartyManagerUD'].getFieldByName(field).aiFormatUpdate(recipient, recipient, simbase.air.ourChannel, values)
|
|
|
|
def sendToAI(self, field, values, sender=None):
|
|
if not sender:
|
|
sender = self.air.getAvatarIdFromSender()
|
|
dg = self._makeAIMsg(field, values, self.senders2Mgrs.get(sender, sender + 8))
|
|
self.air.send(dg)
|
|
|
|
# GPMUD -> toon messaging
|
|
def _makeAvMsg(self, field, values, recipient):
|
|
return self.air.dclassesByName['DistributedToonUD'].getFieldByName(field).aiFormatUpdate(recipient, recipient, simbase.air.ourChannel, values)
|
|
|
|
def sendToAv(self, avId, field, values):
|
|
dg = self._makeAvMsg(field, values, avId)
|
|
self.air.send(dg)
|
|
|
|
# Task stuff
|
|
def runAtNextInterval(self):
|
|
now = datetime.now()
|
|
howLongUntilAFive = (60 - now.second) + 60 * (4 - (now.minute % 5))
|
|
taskMgr.doMethodLater(howLongUntilAFive, self.__checkPartyStarts, 'GlobalPartyManager_checkStarts')
|
|
|
|
def canPartyStart(self, party):
|
|
now = datetime.now()
|
|
delta = timedelta(minutes=15)
|
|
endStartable = party['start'] + delta
|
|
if self.wantInstantParties:
|
|
return True
|
|
else:
|
|
return party['start'] < now# and endStartable > now
|
|
|
|
def isTooLate(self, party):
|
|
now = datetime.now()
|
|
delta = timedelta(minutes=15)
|
|
endStartable = party['start'] + delta
|
|
return endStartable > now
|
|
|
|
def __checkPartyStarts(self, task):
|
|
now = datetime.now()
|
|
for partyId in self.id2Party:
|
|
party = self.id2Party[partyId]
|
|
hostId = party['hostId']
|
|
if self.canPartyStart(party) and party['status'] == PartyStatus.Pending:
|
|
# Time to start party
|
|
party['status'] = PartyStatus.CanStart
|
|
self.sendToAv(hostId, 'setHostedParties', [[self._formatParty(party)]])
|
|
self.sendToAv(hostId, 'setPartyCanStart', [partyId])
|
|
elif self.isTooLate(party):
|
|
party['status'] = PartyStatus.NeverStarted
|
|
self.sendToAv(hostId, 'setHostedParties', [[self._formatParty(party)]])
|
|
self.runAtNextInterval()
|
|
|
|
# Format a party dict into a party struct suitable for the wire
|
|
def _formatParty(self, partyDict):
|
|
start = partyDict['start']
|
|
end = partyDict['end']
|
|
return [partyDict['partyId'],
|
|
partyDict['hostId'],
|
|
start.year,
|
|
start.month,
|
|
start.day,
|
|
start.hour,
|
|
start.minute,
|
|
end.year,
|
|
end.month,
|
|
end.day,
|
|
end.hour,
|
|
end.minute,
|
|
partyDict['isPrivate'],
|
|
partyDict['inviteTheme'],
|
|
partyDict['activities'],
|
|
partyDict['decorations'],
|
|
partyDict.get('status', PartyStatus.Pending)]
|
|
|
|
# Avatar joined the game, invoked by the CSMUD
|
|
def avatarJoined(self, avId):
|
|
# self.host2PartyId[avId] = (1337 << 32) + 10000
|
|
partyId = self.host2PartyId.get(avId, None)
|
|
if partyId:
|
|
party = self.id2Party.get(partyId, None)
|
|
if not party:
|
|
return # There's a partyId without an actual party?? What is this madness.
|
|
self.sendToAv(avId, 'setHostedParties', [[self._formatParty(party)]])
|
|
if partyId not in self.party2PubInfo and self.canPartyStart(party):
|
|
# The party hasn't started and it can start
|
|
self.sendToAv(avId, 'setPartyCanStart', [partyId])
|
|
|
|
# uberdog coordination of public party info
|
|
def __updatePartyInfo(self, partyId):
|
|
# Update all the AIs about this public party
|
|
party = self.party2PubInfo[partyId]
|
|
for sender in self.senders2Mgrs.keys():
|
|
actIds = []
|
|
for activity in self.id2Party[partyId]['activities']:
|
|
actIds.append(activity[0]) # First part of activity tuple should be actId
|
|
minLeft = int((PARTY_DURATION - (datetime.now() - party['started']).seconds) / 60)
|
|
self.sendToAI('updateToPublicPartyInfoUdToAllAi', [party['shardId'], party['zoneId'], partyId, self.id2Party[partyId]['hostId'], party['numGuests'], party['maxGuests'], party['hostName'], actIds, minLeft], sender=sender)
|
|
|
|
def __updatePartyCount(self, partyId):
|
|
# Update the party guest count
|
|
for sender in self.senders2Mgrs.keys():
|
|
self.sendToAI('updateToPublicPartyCountUdToAllAi', [self.party2PubInfo[partyId]['numGuests'], partyId], sender=sender)
|
|
|
|
def partyHasStarted(self, partyId, shardId, zoneId, hostName):
|
|
self.party2PubInfo[partyId] = {'partyId': partyId, 'shardId': shardId, 'zoneId': zoneId, 'hostName': hostName, 'numGuests': 0, 'maxGuests': MaxToonsAtAParty, 'started': datetime.now()}
|
|
self.__updatePartyInfo(partyId)
|
|
# update the host's book
|
|
if partyId not in self.id2Party:
|
|
self.notify.warning("didn't find details for starting party id %s hosted by %s" % (partyId, hostName))
|
|
return
|
|
self.id2Party[partyId]['status'] = PartyStatus.Started
|
|
party = self.id2Party.get(partyId, None)
|
|
self.sendToAv(party['hostId'], 'setHostedParties', [[self._formatParty(party)]])
|
|
|
|
def partyDone(self, partyId):
|
|
del self.party2PubInfo[partyId]
|
|
self.id2Party[partyId]['status'] = PartyStatus.Finished
|
|
party = self.id2Party.get(partyId, None)
|
|
self.sendToAv(party['hostId'], 'setHostedParties', [[self._formatParty(party)]])
|
|
del self.id2Party[partyId]
|
|
self.air.writeServerEvent('party-done', '%s')
|
|
|
|
def toonJoinedParty(self, partyId, avId):
|
|
if avId in self.tempSlots:
|
|
del self.tempSlots[avId]
|
|
return
|
|
self.party2PubInfo.get(partyId, {'numGuests': 0})['numGuests'] += 1
|
|
self.__updatePartyCount(partyId)
|
|
|
|
def toonLeftParty(self, partyId, avId):
|
|
self.party2PubInfo.get(partyId, {'numGuests': 0})['numGuests'] -= 1
|
|
self.__updatePartyCount(partyId)
|
|
|
|
def partyManagerAIHello(self, channel):
|
|
# Upon AI boot, DistributedPartyManagerAIs are supposed to say hello.
|
|
# They send along the DPMAI's doId as well, so that I can talk to them later.
|
|
print 'AI with base channel %s, will send replies to DPM %s' % (simbase.air.getAvatarIdFromSender(), channel)
|
|
self.senders2Mgrs[simbase.air.getAvatarIdFromSender()] = channel
|
|
self.sendToAI('partyManagerUdStartingUp', [])
|
|
|
|
# In addition, set up a postRemove where we inform this AI that the UD has died
|
|
self.air.addPostRemove(self._makeAIMsg('partyManagerUdLost', [], channel))
|
|
|
|
def addParty(self, avId, partyId, start, end, isPrivate, inviteTheme, activities, decorations, inviteeIds):
|
|
PARTY_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
print 'start time: %s' % start
|
|
startTime = datetime.strptime(start, PARTY_TIME_FORMAT)
|
|
endTime = datetime.strptime(end, PARTY_TIME_FORMAT)
|
|
print 'start year: %s' % startTime.year
|
|
if avId in self.host2PartyId:
|
|
# Sorry, one party at a time
|
|
self.sendToAI('addPartyResponseUdToAi', [partyId, AddPartyErrorCode.TooManyHostedParties, self._formatParty(self.id2Party[partyId])])
|
|
self.id2Party[partyId] = {'partyId': partyId, 'hostId': avId, 'start': startTime, 'end': endTime, 'isPrivate': isPrivate, 'inviteTheme': inviteTheme, 'activities': activities, 'decorations': decorations, 'inviteeIds': inviteeIds, 'status': PartyStatus.Pending}
|
|
self.host2PartyId[avId] = partyId
|
|
self.sendToAI('addPartyResponseUdToAi', [partyId, AddPartyErrorCode.AllOk, self._formatParty(self.id2Party[partyId])])
|
|
if self.wantInstantParties:
|
|
taskMgr.remove('GlobalPartyManager_checkStarts')
|
|
taskMgr.doMethodLater(15, self.__checkPartyStarts, 'GlobalPartyManager_checkStarts')
|
|
return
|
|
|
|
def queryParty(self, hostId):
|
|
# An AI is wondering if the host has a party. We'll tell em!
|
|
if hostId in self.host2PartyId:
|
|
# Yep, he has a party.
|
|
party = self.id2Party[self.host2PartyId[hostId]]
|
|
self.sendToAI('partyInfoOfHostResponseUdToAi', [self._formatParty(party), party.get('inviteeIds', [])])
|
|
return
|
|
print 'query failed, av %s isnt hosting anything' % hostId
|
|
|
|
def requestPartySlot(self, partyId, avId, gateId):
|
|
if partyId not in self.party2PubInfo:
|
|
recipient = self.GetPuppetConnectionChannel(avId)
|
|
sender = simbase.air.getAvatarIdFromSender()
|
|
dg = self.air.dclassesByName['DistributedPartyGateAI'].getFieldByName('partyRequestDenied').aiFormatUpdate(gateId, recipient, sender, [PartyGateDenialReasons.Unavailable])
|
|
self.air.send(dg)
|
|
return
|
|
party = self.party2PubInfo[partyId]
|
|
if party['numGuests'] >= party['maxGuests']:
|
|
recipient = self.GetPuppetConnectionChannel(avId)
|
|
sender = simbase.air.getAvatarIdFromSender()
|
|
dg = self.air.dclassesByName['DistributedPartyGateAI'].getFieldByName('partyRequestDenied').aiFormatUpdate(gateId, recipient, sender, [PartyGateDenialReasons.Full])
|
|
self.air.send(dg)
|
|
return
|
|
# get them a slot
|
|
party['numGuests'] += 1
|
|
self.__updatePartyCount(partyId)
|
|
# note that they might not show up
|
|
self.tempSlots[avId] = partyId
|
|
|
|
#give the client a minute to connect before freeing their slot
|
|
taskMgr.doMethodLater(60, self._removeTempSlot, 'partyManagerTempSlot%d' % avId, extraArgs=[avId])
|
|
|
|
# now format the pubPartyInfo
|
|
actIds = []
|
|
for activity in self.id2Party[partyId]['activities']:
|
|
actIds.append(activity[0])
|
|
info = [party['shardId'], party['zoneId'], party['numGuests'], party['hostName'], actIds, 0] # the param is minleft
|
|
# find the hostId
|
|
hostId = self.id2Party[party['partyId']]['hostId']
|
|
# send update to client's gate
|
|
recipient = self.GetPuppetConnectionChannel(avId)
|
|
sender = simbase.air.getAvatarIdFromSender() # try to pretend the AI sent it. ily2 cfsworks
|
|
dg = self.air.dclassesByName['DistributedPartyGateAI'].getFieldByName('setParty').aiFormatUpdate(gateId, recipient, sender, [info, hostId])
|
|
self.air.send(dg)
|
|
|
|
def _removeTempSlot(self, avId):
|
|
partyId = self.tempSlots.get(avId)
|
|
if partyId:
|
|
del self.tempSlots[avId]
|
|
self.party2PubInfo.get(partyId, {'numGuests': 0})['numGuests'] -= 1
|
|
self.__updatePartyCount(partyId)
|
|
|
|
def allocIds(self, numIds):
|
|
ids = []
|
|
while len(ids) < numIds:
|
|
ids.append(self.nextId)
|
|
self.nextId += 1
|
|
self.sendToAI('receiveId', ids)
|