oldschool-toontown/toontown/cogdominium/DistCogdoMazeGameAI.py

484 lines
20 KiB
Python
Raw Normal View History

from panda3d.core import Vec3, NodePath
2019-11-02 17:27:54 -05:00
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
2019-11-02 17:27:54 -05:00
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 Globals.SuitTypes:
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestSuitHitByGag: invalid suit type %s' % suitType)
return False
if suitNum not in list(self.suits.keys()):
2019-11-02 17:27:54 -05:00
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 Globals.SuitTypes:
self.logSuspiciousEvent(senderId, 'CogdoMazeGameAI.requestHitBySuit: invalid suit type %s' % suitType)
return False
if suitNum not in list(self.suits.keys()):
2019-11-02 17:27:54 -05:00
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():
2019-11-02 17:27:54 -05:00
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