oldschool-toontown/toontown/cogdominium/DistCogdoMazeGameAI.py

483 lines
20 KiB
Python

from panda3d.core import Vec3, NodePath
from direct.distributed.ClockDelta import globalClockDelta
from otp.avatar.SpeedMonitor import SpeedMonitor
from toontown.cogdominium.CogdoMaze import CogdoMazeFactory
from toontown.cogdominium.DistCogdoMazeGameBase import DistCogdoMazeGameBase
from .DistCogdoGameAI import DistCogdoGameAI
from . import CogdoMazeGameGlobals as Globals
cogdoMazeTimeScoreRatio = 0.5
cogdoMazePerfectTime = 90
cogdoMazeMaxTime = 210
cogdoMazePickupScoreRatio = 0.7
class DistCogdoMazeGameAI(DistCogdoGameAI, DistCogdoMazeGameBase):
notify = directNotify.newCategory('DistCogdoMazeGameAI')
TimerExpiredTaskName = 'CMG_TimerExpiredTask'
TimeoutTimerTaskName = 'CMG_timeoutTimerTask'
CountdownTimerTaskName = 'CMG_countdownTimerTask'
AnnounceGameDoneTimerTaskName = 'CMG_AnnounceGameDoneTimerTask'
SkipCogdoGames = simbase.config.GetBool('skip-cogdo-game', 0)
def __init__(self, air, id):
DistCogdoGameAI.__init__(self, air, id)
self.doorIsOpen = False
self.toonsInDoor = []
self.pickups = []
self.pickupsDropped = 0
self.maxPickups = 0
self.numPickedUp = 0
self.suits = {}
self.lastRequestId = None
self.requestStartTime = globalClock.getFrameTime()
self.requestCount = None
self.jokeLastRequestId = None
self.jokeRequestStartTime = globalClock.getFrameTime()
self.jokeRequestCount = None
if __debug__ and simbase.config.GetBool('schellgames-dev', True):
self.accept('onCodeReload', self.__sgOnCodeReload)
def setExteriorZone(self, exteriorZone):
DistCogdoGameAI.setExteriorZone(self, exteriorZone)
self.difficulty = self.getDifficulty()
self.createSuits()
def createSuits(self):
serialNum = 0
self._numSuits = []
extraSuits = 0
for i in range(len(Globals.NumSuits)):
extraSuits = int(round(self.difficulty * Globals.SuitsModifier[i]))
self._numSuits.append(Globals.NumSuits[i] + extraSuits)
self.bosses = self._numSuits[0]
for i in range(self._numSuits[0]):
self.suits[serialNum] = Globals.SuitData[Globals.SuitTypes.Boss]['hp']
serialNum += 1
for i in range(self._numSuits[1]):
self.suits[serialNum] = Globals.SuitData[Globals.SuitTypes.FastMinion]['hp']
serialNum += 1
for i in range(self._numSuits[2]):
self.suits[serialNum] = Globals.SuitData[Globals.SuitTypes.SlowMinion]['hp']
serialNum += 1
self._totalSuits = serialNum
self.maxPickups = self._numSuits[0] * Globals.SuitData[0]['memos']
self.maxPickups += self._numSuits[1] * Globals.SuitData[1]['memos']
self.maxPickups += self._numSuits[2] * Globals.SuitData[2]['memos']
def generate(self):
DistCogdoGameAI.generate(self)
mazeFactory = self.createMazeFactory(self.createRandomNumGen())
waterCoolerList = []
mazeModel = mazeFactory._loadAndBuildMazeModel()
for waterCooler in mazeModel.findAllMatches('**/*waterCooler'):
waterCoolerList.append((waterCooler.getPos(mazeModel), waterCooler.getHpr(mazeModel)))
waterCoolerList.sort()
baseNp = NodePath('base')
rotNp = baseNp.attachNewNode('rot')
childNp = rotNp.attachNewNode('child')
childNp.setPos(*Globals.WaterCoolerTriggerOffset)
self._waterCoolerPosList = []
for (pos, hpr) in waterCoolerList:
rotNp.setHpr(hpr)
offset = childNp.getPos(baseNp)
self._waterCoolerPosList.append(pos + offset)
self._speedMonitor = SpeedMonitor('cogdoMazeGame-%s' % self.doId)
self._toonId2speedToken = {}
def delete(self):
self.ignoreAll()
self._speedMonitor.destroy()
DistCogdoGameAI.delete(self)
def isDoorOpen(self):
return self.doorIsOpen
def isToonInDoor(self, toonId):
return toonId in self.toonsInDoor
def areAllToonsInDoor(self):
return self.getNumPlayers() == len(self.toonsInDoor)
def getCurrentNetworkTime(self):
return globalClockDelta.localToNetworkTime(globalClock.getRealTime())
def suitHit(self, suitType, suitNum):
avId = simbase.air.getAvatarIdFromSender()
toon = simbase.air.doId2do.get(avId)
if not toon:
simbase.air.writeServerEvent('suspicious', avId, 'CogdoMazeGame.suitHit: toon not present?')
return False
result = True
if self.lastRequestId == avId:
self.requestCount += 1
now = globalClock.getFrameTime()
elapsed = now - self.requestStartTime
if elapsed > 10:
self.requestCount = 1
self.requestStartTime = now
else:
secondsPerGrab = elapsed / self.requestCount
if self.requestCount >= 3 and secondsPerGrab <= 0.4:
simbase.air.writeServerEvent('suspicious', avId, 'suitHit %s suits in %s seconds' % (self.requestCount, elapsed))
if simbase.config.GetBool('want-ban-cogdo-maze-suit-hit', False):
toon.ban('suitHit %s suits in %s seconds' % (self.requestCount, elapsed))
result = False
else:
self.lastRequestId = avId
self.requestCount = 1
self.requestStartTime = globalClock.getFrameTime()
if result:
self.suits[suitNum] -= 1
hp = self.suits[suitNum]
if hp <= 0:
self.suitDestroyed(suitType, suitNum)
return result
def suitDestroyed(self, suitType, suitNum):
if suitType == Globals.SuitTypes.Boss:
self.bosses -= 1
if self.bosses <= 0:
self.openDoor()
self.createPickups(suitType)
del self.suits[suitNum]
def createPickups(self, suitType):
for i in range(Globals.SuitData[suitType]['memos']):
self.pickups.append(self.pickupsDropped)
self.pickupsDropped += 1
def _removeToonFromGame(self, toonId):
if self.fsm.getCurrentState().getName() == 'Game':
if toonId not in self._toonId2speedToken:
simbase.air.writeServerEvent('avoid_crash', toonId, 'CogdoMazeGame._removeToonFromGame: toon not in _toonId2speedToken')
else:
token = self._toonId2speedToken.pop(toonId)
self._speedMonitor.removeNodepath(token)
if self.areAllToonsInDoor():
self._handleGameFinished()
def handleToonDisconnected(self, toonId):
DistCogdoGameAI.handleToonDisconnected(self, toonId)
self._removeToonFromGame(toonId)
def handleToonWentSad(self, toonId):
DistCogdoGameAI.handleToonWentSad(self, toonId)
self._removeToonFromGame(toonId)
def getNumSuits(self):
return list(self._numSuits)
def d_broadcastPickup(self, senderId, pickupNum, networkTime):
self.sendUpdate('pickUp', [
senderId,
pickupNum,
networkTime])
def requestAction(self, action, data):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestAction outside of Game state')
return False
if action == Globals.GameActions.EnterDoor:
if not self.doorIsOpen:
self.logSuspiciousEvent(senderId, "CogdoMazeGameAI.requestAction(EnterDoor): door isn't open yet")
elif senderId not in self.toonsInDoor:
self.toonsInDoor.append(senderId)
self.d_broadcastDoAction(action, senderId)
if self.areAllToonsInDoor():
self._handleGameFinished()
else:
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestAction: toon already in door')
elif action == Globals.GameActions.RevealDoor:
self.d_broadcastDoAction(action, senderId)
else:
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestAction: invalid action %s' % action)
def d_broadcastDoAction(self, action, data = 0, networkTime = 0):
self.sendUpdate('doAction', [
action,
data,
networkTime])
def requestUseGag(self, x, y, h, networkTime):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestUseGag outside of Game state')
return False
self.d_broadcastToonUsedGag(senderId, x, y, h, networkTime)
def d_broadcastToonUsedGag(self, toonId, x, y, h, networkTime):
self.sendUpdate('toonUsedGag', [
toonId,
x,
y,
h,
networkTime])
def requestSuitHitByGag(self, suitType, suitNum):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestSuitHitByGag outside of Game state')
return False
if suitType not in list(Globals.SuitTypes):
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestSuitHitByGag: invalid suit type %s' % suitType)
return False
if suitNum not in list(self.suits.keys()):
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestSuitHitByGag: invalid suit num %s' % suitNum)
return False
resultValid = self.suitHit(suitType, suitNum)
if resultValid:
self.d_broadcastSuitHitByGag(senderId, suitType, suitNum)
def d_broadcastSuitHitByGag(self, toonId, suitType, suitNum):
self.sendUpdate('suitHitByGag', [
toonId,
suitType,
suitNum])
def requestHitBySuit(self, suitType, suitNum, networkTime):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestHitBySuit outside of Game state')
return False
if suitType not in list(Globals.SuitTypes):
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestHitBySuit: invalid suit type %s' % suitType)
return False
if suitNum not in list(self.suits.keys()):
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestHitBySuit: invalid suit num %s' % suitNum)
return False
toon = self.air.doId2do[senderId]
self.d_broadcastToonHitBySuit(senderId, suitType, suitNum, networkTime)
damage = Globals.SuitData[suitType]['toonDamage']
damage += int(round(damage * Globals.DamageModifier * self.difficulty))
toon.takeDamage(damage, quietly = 0)
def d_broadcastToonHitBySuit(self, toonId, suitType, suitNum, networkTime):
self.sendUpdate('toonHitBySuit', [
toonId,
suitType,
suitNum,
networkTime])
def requestHitByDrop(self):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestHitByDrop outside of Game state')
return False
toon = self.air.doId2do[senderId]
self.d_broadcastToonHitByDrop(senderId)
if Globals.DropDamage > 0:
damage = Globals.DropDamage
damage += int(round(damage * Globals.DamageModifier * self.difficulty))
toon.takeDamage(damage, quietly = 0)
def d_broadcastToonHitByDrop(self, toonId):
self.sendUpdate('toonHitByDrop', [
toonId])
def requestGag(self, waterCoolerIndex):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestGag outside of Game state')
return False
if waterCoolerIndex >= len(self._waterCoolerPosList):
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestGag: invalid waterCoolerIndex')
return
wcPos = self._waterCoolerPosList[waterCoolerIndex]
toon = self.air.doId2do.get(senderId)
if not toon:
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestGag: toon not present')
return
distance = (toon.getPos() - wcPos).length()
threshold = (Globals.WaterCoolerTriggerRadius + Globals.PlayerCollisionRadius) * 1.05
if distance > threshold:
self._toonHackingRequestGag(senderId)
return
self.d_broadcastHasGag(senderId, self.getCurrentNetworkTime())
def d_broadcastHasGag(self, senderId, networkTime):
self.sendUpdate('hasGag', [
senderId,
networkTime])
def requestPickUp(self, pickupNum):
senderId = self.air.getAvatarIdFromSender()
if not self._validateSenderId(senderId):
return False
if self.fsm.getCurrentState().getName() != 'Game':
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestPickUp outside of Game state')
return False
if pickupNum not in self.pickups:
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestPickUp: invalid pickupNum %s' % pickupNum)
return False
toon = simbase.air.doId2do.get(senderId)
if not toon:
simbase.air.writeServerEvent('suspicious', senderId, 'CogdoMazeGame.requestPickUp: toon not present?')
return False
result = True
if self.jokeLastRequestId == senderId:
self.jokeRequestCount += 1
now = globalClock.getFrameTime()
elapsed = now - self.jokeRequestStartTime
if elapsed > 10:
self.jokeRequestCount = 1
self.jokeRequestStartTime = now
else:
secondsPerGrab = elapsed / self.jokeRequestCount
if self.jokeRequestCount >= 4 and secondsPerGrab <= 0.03:
simbase.air.writeServerEvent('suspicious', senderId, 'requestPickup %s jokes in %s seconds' % (self.jokeRequestCount, elapsed))
if simbase.config.GetBool('want-ban-cogdo-maze-request-pickup', False):
toon.ban('requestPickup %s jokes in %s seconds' % (self.jokeRequestCount, elapsed))
result = False
else:
self.jokeLastRequestId = senderId
self.jokeRequestCount = 1
self.jokeRequestStartTime = globalClock.getFrameTime()
if result:
self.numPickedUp += 1
self.pickups.remove(pickupNum)
self.d_broadcastPickup(senderId, pickupNum, self.getCurrentNetworkTime())
def enterGame(self):
DistCogdoGameAI.enterGame(self)
endTime = Globals.SecondsUntilTimeout - (globalClock.getRealTime() - self.getStartTime())
self._countdownTimerTask = taskMgr.doMethodLater(endTime - Globals.SecondsForTimeAlert, self.handleCountdownTimer, self.taskName(DistCogdoMazeGameAI.CountdownTimerTaskName), [])
self._startGameTimer()
self._timeoutTimerTask = taskMgr.doMethodLater(endTime, self.handleEndGameTimerExpired, self.taskName(DistCogdoMazeGameAI.TimeoutTimerTaskName), [])
if self.SkipCogdoGames:
self.fsm.request('Finish')
for toonId in self.getToonIds():
toon = self.air.doId2do.get(toonId)
if toon:
token = self._speedMonitor.addNodepath(toon)
self._toonId2speedToken[toonId] = token
self._speedMonitor.setSpeedLimit(token, config.GetFloat('cogdo-maze-speed-limit', Globals.ToonRunSpeed * 1.1), Functor(self._toonOverSpeedLimit, toonId))
def _toonOverSpeedLimit(self, toonId, speed):
self._bootPlayerForHacking(toonId, 'speeding in cogdo maze game (%.2f feet/sec)' % speed, config.GetBool('want-ban-cogdo-maze-speeding', 0))
def _toonHackingRequestGag(self, toonId):
simbase.air.writeServerEvent('suspicious', toonId, 'CogdoMazeGame: toon caught hacking requestGag')
self._bootPlayerForHacking(toonId, 'hacking cogdo maze game requestGag', config.GetBool('want-ban-cogdo-maze-requestgag-hacking', 0))
def _bootPlayerForHacking(self, toonId, reason, wantBan):
toon = simbase.air.doId2do.get(toonId)
if not toon:
simbase.air.writeServerEvent('suspicious', toonId, 'CogdoMazeGame._bootPlayerForHacking(%s): toon not present' % (reason,))
return
if wantBan:
toon.ban(reason)
def _removeEndGameTimerTask(self):
if hasattr(self, '_gameTimerExpiredTask'):
taskMgr.remove(self._gameTimerExpiredTask)
del self._gameTimerExpiredTask
def _removeTimeoutTimerTask(self):
if hasattr(self, '_timeoutTimerTask'):
taskMgr.remove(self._timeoutTimerTask)
del self._timeoutTimerTask
def _removeCountdownTimerTask(self):
if hasattr(self, '_countdownTimerTask'):
taskMgr.remove(self._countdownTimerTask)
del self._countdownTimerTask
def _startGameTimer(self):
self.d_broadcastDoAction(Globals.GameActions.Countdown, networkTime = self.getCurrentNetworkTime())
def openDoor(self):
self._removeTimeoutTimerTask()
self._removeCountdownTimerTask()
self.doorIsOpen = True
self.d_broadcastDoAction(Globals.GameActions.OpenDoor, networkTime = self.getCurrentNetworkTime())
self._gameTimerExpiredTask = taskMgr.doMethodLater(Globals.SecondsUntilGameEnds, self.handleEndGameTimerExpired, self.taskName(DistCogdoMazeGameAI.TimerExpiredTaskName), [])
def handleCountdownTimer(self):
self.d_broadcastDoAction(Globals.GameActions.TimeAlert, networkTime = self.getCurrentNetworkTime())
def handleEndGameTimerExpired(self):
self._handleGameFinished()
def exitGame(self):
DistCogdoGameAI.exitGame(self)
for (toonId, token) in self._toonId2speedToken.items():
self._speedMonitor.removeNodepath(token)
self._toonId2speedToken = {}
self._removeTimeoutTimerTask()
self._removeEndGameTimerTask()
def enterFinish(self):
DistCogdoGameAI.enterFinish(self)
if self.numPickedUp > self.maxPickups:
self.logSuspiciousEvent(0, 'CogdoMazeGameAI: collected more memos than possible: %s, players: %s' % (self.numPickedUp, self.getToonIds()))
time = globalClock.getRealTime() - self.getStartTime()
adjustedTime = min(max(time - cogdoMazePerfectTime, 0), cogdoMazeMaxTime)
timeScore = 1 - adjustedTime / cogdoMazeMaxTime
pickupScore = 0
if self.maxPickups:
pickupScore = float(self.numPickedUp) / self.maxPickups
weightedPickup = pickupScore * cogdoMazePickupScoreRatio
weightedTime = timeScore * cogdoMazeTimeScoreRatio
score = min(weightedPickup + weightedTime, 1.0)
self.air.writeServerEvent('CogdoMazeGame', self._interior.toons, 'Memos: %s/%s Weighted Memos: %s Time: %s Weighted Time: %s Score: %s' % (self.numPickedUp, self.maxPickups, weightedPickup, time, weightedTime, score))
self.setScore(score)
self._announceGameDoneTask = taskMgr.doMethodLater(Globals.FinishDurationSeconds, self.announceGameDone, self.taskName(DistCogdoMazeGameAI.AnnounceGameDoneTimerTaskName), [])
def exitFinish(self):
DistCogdoGameAI.exitFinish(self)
taskMgr.remove(self._announceGameDoneTask)
del self._announceGameDoneTask