toontown-just-works/toontown/cogdominium/CogdoMazeGame.py
2024-07-07 18:08:39 -05:00

582 lines
21 KiB
Python

from pandac.PandaModules import Point3, CollisionSphere, CollisionNode
from direct.showbase.DirectObject import DirectObject
from direct.showbase.PythonUtil import Functor
from direct.showbase.RandomNumGen import RandomNumGen
from direct.task.Task import Task
from toontown.minigame.MazeSuit import MazeSuit
from toontown.toonbase import ToontownGlobals
from CogdoGameGatherable import CogdoMemo
from CogdoMazePlayer import CogdoMazePlayer
from CogdoMazeLocalPlayer import CogdoMazeLocalPlayer
from CogdoMazeGuiManager import CogdoMazeGuiManager
from CogdoGameAudioManager import CogdoGameAudioManager
from CogdoMazeGameObjects import CogdoMazeExit, CogdoMazeDrop
from CogdoMazeSuits import CogdoMazeSuit, CogdoMazeSlowMinionSuit, CogdoMazeFastMinionSuit, CogdoMazeBossSuit
from CogdoMazeGameMovies import CogdoMazeGameIntro, CogdoMazeGameFinish
import CogdoMazeGameGlobals as Globals
import CogdoUtil
import math
import random
class CogdoMazeGame(DirectObject):
notify = directNotify.newCategory('CogdoMazeGame')
UpdateTaskName = 'CogdoMazeGameUpdateTask'
RemoveGagTaskName = 'CogdoMazeGameRemoveGag'
PlayerCoolerCollision = '%s-into-%s' % (Globals.LocalPlayerCollisionName, Globals.WaterCoolerCollisionName)
PlayerDropCollision = '%s-into-%s' % (Globals.LocalPlayerCollisionName, Globals.DropCollisionName)
def __init__(self, distGame):
self.distGame = distGame
self._allowSuitsHitToons = base.config.GetBool('cogdomaze-suits-hit-toons', True)
def load(self, cogdoMazeFactory, numSuits, bossCode):
self._initAudio()
self.maze = cogdoMazeFactory.createCogdoMaze()
suitSpawnSpot = self.maze.createRandomSpotsList(numSuits, self.distGame.randomNumGen)
self.guiMgr = CogdoMazeGuiManager(self.maze, bossCode)
self.suits = []
self.suitsById = {}
self.shakers = []
self.toonsThatRevealedDoor = []
self.quake = 0
self.dropCounter = 0
self.drops = {}
self.gagCounter = 0
self.gags = []
self.hackTemp = False
self.dropGen = RandomNumGen(self.distGame.doId)
self.gagTimeoutTasks = []
self.finished = False
self.lastBalloonTimestamp = None
difficulty = self.distGame.getDifficulty()
serialNum = 0
for i in xrange(numSuits[0]):
suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
suit = CogdoMazeBossSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[0][i])
self.addSuit(suit)
self.guiMgr.mazeMapGui.addSuit(suit.suit)
serialNum += 1
for i in xrange(numSuits[1]):
suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
suit = CogdoMazeFastMinionSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[1][i])
self.addSuit(suit)
serialNum += 1
for i in xrange(numSuits[2]):
suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
suit = CogdoMazeSlowMinionSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[2][i])
self.addSuit(suit)
serialNum += 1
self.toonId2Door = {}
self.keyIdToKey = {}
self.players = []
self.toonId2Player = {}
cellPos = (int(self.maze.width / 2), self.maze.height - 1)
pos = self.maze.tile2world(*cellPos)
self._exit = CogdoMazeExit()
self._exit.reparentTo(render)
self._exit.setPos(self.maze.exitPos)
self._exit.stash()
self.guiMgr.mazeMapGui.placeExit(*cellPos)
self._collNode2waterCooler = {}
for waterCooler in self.maze.getWaterCoolers():
pos = waterCooler.getPos(render)
tpos = self.maze.world2tile(pos[0], pos[1])
self.guiMgr.mazeMapGui.addWaterCooler(*tpos)
self._collNode2waterCooler[waterCooler.collNode] = waterCooler
self.pickups = []
self.gagModel = CogdoUtil.loadMazeModel('waterBalloon')
self._movie = CogdoMazeGameIntro(self.maze, self._exit, self.distGame.randomNumGen)
self._movie.load()
return
def _initAudio(self):
self._audioMgr = CogdoGameAudioManager(Globals.MusicFiles, Globals.SfxFiles, camera, cutoff=Globals.AudioCutoff)
self._quakeSfx1 = self._audioMgr.createSfx('quake')
self._quakeSfx2 = self._audioMgr.createSfx('quake')
def _destroyAudio(self):
self._quakeSfx1.destroy()
self._quakeSfx2.destroy()
del self._quakeSfx1
del self._quakeSfx2
self._audioMgr.destroy()
del self._audioMgr
def addSuit(self, suit):
id = suit.serialNum
self.suits.append(suit)
if suit.type == Globals.SuitTypes.Boss:
self.shakers.append(suit)
self.suitsById[id] = suit
def removeSuit(self, suit):
id = suit.serialNum
del self.suitsById[id]
if suit.type == Globals.SuitTypes.Boss:
self.shakers.remove(suit)
self.guiMgr.showBossCode(id)
self.guiMgr.mazeMapGui.removeSuit(suit.suit)
self.suits.remove(suit)
suit.destroy()
def unload(self):
self.toonsThatRevealedDoor = []
for suit in self.suits:
suit.destroy()
del self.suits
for id in self.drops.keys():
self.cleanupDrop(id)
self.__stopUpdateTask()
self.ignoreAll()
self.maze.destroy()
del self.maze
self._exit.destroy()
del self._exit
self.guiMgr.destroy()
del self.guiMgr
for player in self.players:
player.destroy()
del self.players
del self.toonId2Player
del self.localPlayer
for pickup in self.pickups:
pickup.destroy()
self._destroyAudio()
del self.distGame
def onstage(self):
self._exit.onstage()
self.maze.onstage()
def offstage(self):
self.maze.offstage()
self._exit.offstage()
for suit in self.suits:
suit.offstage()
def startIntro(self):
self._movie.play()
self._audioMgr.playMusic('normal')
def endIntro(self):
self._movie.end()
self._movie.unload()
del self._movie
base.camLens.setMinFov(ToontownGlobals.CogdoFov/(4./3.))
for player in self.players:
self.placePlayer(player)
if player.toon is localAvatar:
localAvatar.sendCurrentPosition()
player.request('Ready')
def startFinish(self):
self._movie = CogdoMazeGameFinish(self.localPlayer, self._exit)
self._movie.load()
self._movie.play()
def endFinish(self):
self._movie.end()
self._movie.unload()
del self._movie
def placeEntranceElevator(self, elevator):
pos = self.maze.elevatorPos
elevator.setPos(pos)
tpos = self.maze.world2tile(pos[0], pos[1])
self.guiMgr.mazeMapGui.placeEntrance(*tpos)
def initPlayers(self):
for toonId in self.distGame.getToonIds():
toon = self.distGame.getToon(toonId)
if toon is not None:
if toon.isLocal():
player = CogdoMazeLocalPlayer(len(self.players), base.localAvatar, self, self.guiMgr)
self.localPlayer = player
else:
player = CogdoMazePlayer(len(self.players), toon)
self._addPlayer(player)
return
def start(self):
self.accept(self.PlayerDropCollision, self.handleLocalToonMeetsDrop)
self.accept(self.PlayerCoolerCollision, self.handleLocalToonMeetsWaterCooler)
self.accept(CogdoMazeExit.EnterEventName, self.handleLocalToonEntersDoor)
self.accept(CogdoMemo.EnterEventName, self.handleLocalToonMeetsPickup)
if self._allowSuitsHitToons:
self.accept(CogdoMazeSuit.COLLISION_EVENT_NAME, self.handleLocalToonMeetsSuit)
self.accept(CogdoMazePlayer.GagHitEventName, self.handleToonMeetsGag)
self.accept(CogdoMazeSuit.GagHitEventName, self.handleLocalSuitMeetsGag)
self.accept(CogdoMazeSuit.DeathEventName, self.handleSuitDeath)
self.accept(CogdoMazeSuit.ThinkEventName, self.handleSuitThink)
self.accept(CogdoMazeBossSuit.ShakeEventName, self.handleBossShake)
self.accept(CogdoMazePlayer.RemovedEventName, self._removePlayer)
self.__startUpdateTask()
for player in self.players:
player.handleGameStart()
player.request('Normal')
for suit in self.suits:
suit.onstage()
suit.gameStart(self.distGame.getStartTime())
def exit(self):
self._quakeSfx1.stop()
self._quakeSfx2.stop()
for suit in self.suits:
suit.gameEnd()
self.ignore(self.PlayerDropCollision)
self.ignore(self.PlayerCoolerCollision)
self.ignore(CogdoMazeExit.EnterEventName)
self.ignore(CogdoMemo.EnterEventName)
self.ignore(CogdoMazeSuit.COLLISION_EVENT_NAME)
self.ignore(CogdoMazePlayer.GagHitEventName)
self.ignore(CogdoMazeSuit.GagHitEventName)
self.ignore(CogdoMazeSuit.DeathEventName)
self.ignore(CogdoMazeSuit.ThinkEventName)
self.ignore(CogdoMazeBossSuit.ShakeEventName)
self.ignore(CogdoMazePlayer.RemovedEventName)
self.__stopUpdateTask()
for timeoutTask in self.gagTimeoutTasks:
taskMgr.remove(timeoutTask)
self.finished = True
self.localPlayer.handleGameExit()
for player in self.players:
player.request('Done')
self.guiMgr.hideTimer()
self.guiMgr.hideMazeMap()
self.guiMgr.hideBossGui()
def _addPlayer(self, player):
self.players.append(player)
self.toonId2Player[player.toon.doId] = player
def _removePlayer(self, player):
if player in self.players:
self.players.remove(player)
else:
for cPlayer in self.players:
if cPlayer.toon == player.toon:
self.players.remove(cPlayer)
break
if player.toon.doId in self.toonId2Player:
del self.toonId2Player[player.toon.doId]
self.guiMgr.mazeMapGui.removeToon(player.toon)
def handleToonLeft(self, toonId):
self._removePlayer(self.toonId2Player[toonId])
def __startUpdateTask(self):
self.__stopUpdateTask()
taskMgr.add(self.__updateTask, CogdoMazeGame.UpdateTaskName, 45)
def __stopUpdateTask(self):
taskMgr.remove(CogdoMazeGame.UpdateTaskName)
taskMgr.remove('loopSecondQuakeSound')
def __updateTask(self, task):
dt = globalClock.getDt()
self.localPlayer.update(dt)
for player in self.players:
curTX, curTY = self.maze.world2tileClipped(player.toon.getX(), player.toon.getY())
self.guiMgr.mazeMapGui.updateToon(player.toon, curTX, curTY)
self.__updateGags()
if Globals.QuakeSfxEnabled:
self.__updateQuakeSound()
for pickup in self.pickups:
pickup.update(dt)
MazeSuit.thinkSuits(self.suits, self.distGame.getStartTime())
return Task.cont
def __updateGags(self):
remove = []
for i in xrange(len(self.gags)):
balloon = self.gags[i]
if balloon.isSingleton():
remove.append(i)
elif balloon.getParent() == render:
loc = self.maze.world2tile(balloon.getX(), balloon.getY())
if not self.maze.isAccessible(loc[0], loc[1]):
self.removeGag(balloon)
remove.reverse()
for i in remove:
self.gags.pop(i)
def __updateQuakeSound(self):
shake = self.localPlayer.getCameraShake()
if shake < 1.0:
shake = 1.0
volume = 3.0 * shake / Globals.CameraShakeMax
if volume > 3.0:
volume = 3.0
if self._quakeSfx1.getAudioSound().status() != self._quakeSfx1.getAudioSound().PLAYING:
self._quakeSfx1.loop(volume=volume)
else:
self._quakeSfx1.getAudioSound().setVolume(volume)
volume = shake * shake / Globals.CameraShakeMax
if not self.hackTemp and self._quakeSfx2.getAudioSound().status() != self._quakeSfx2.getAudioSound().PLAYING:
taskMgr.doMethodLater(1.5, self._quakeSfx2.loop, 'loopSecondQuakeSound', extraArgs=[])
self.hackTemp = True
else:
self._quakeSfx2.getAudioSound().setVolume(volume)
def handleLocalToonMeetsSuit(self, suitType, suitNum):
if self.localPlayer.state == 'Normal' and not self.localPlayer.invulnerable:
self.distGame.b_toonHitBySuit(suitType, suitNum)
def toonHitBySuit(self, toonId, suitType, suitNum, elapsedTime = 0.0):
player = self.toonId2Player[toonId]
if player.state == 'Normal':
player.request('Hit', elapsedTime)
def handleSuitDeath(self, suitType, suitNum):
suit = self.suitsById[suitNum]
self.dropMemos(suit)
self.removeSuit(suit)
def dropMemos(self, suit):
numDrops = suit.memos
if numDrops > 0:
start = math.radians(random.randint(0, 360))
step = math.radians(360.0 / numDrops)
radius = 2.0
for i in xrange(numDrops):
angle = start + i * step
x = radius * math.cos(angle) + suit.suit.getX()
y = radius * math.sin(angle) + suit.suit.getY()
self.generatePickup(x, y)
def handleSuitThink(self, suit, TX, TY):
if suit in self.shakers:
self.guiMgr.mazeMapGui.updateSuit(suit.suit, TX, TY)
suit.dropTimer += 1
if suit.dropTimer >= Globals.DropFrequency:
self.doDrop(suit)
suit.dropTimer = 0
def doDrop(self, boss):
dropLoc = boss.pickRandomValidSpot()
self.generateDrop(dropLoc[0], dropLoc[1])
def handleBossShake(self, suit, strength):
if Globals.BossShakeEnabled:
self.shakeCamera(suit.suit, strength, Globals.BossMaxDistance)
def randomDrop(self, centerTX, centerTY, radius):
dropArray = []
for i in xrange(1, distance):
dropArray.append(i)
dropArray.append(-1 * i)
offsetTX = self.distGame.randomNumGen.choice(dropArray)
offsetTY = self.distGame.randomNumGen.choice(dropArray)
dropTX = sourceTX + offsetTX
dropTY = sourceTY + offsetTY
if self.maze.isWalkable(dropTX, dropTY):
self.generateDrop(dropTX, dropTY)
def generateDrop(self, TX, TY):
drop = self.maze.tile2world(TX, TY)
ival = self.createDrop(drop[0], drop[1])
ival.start()
def createDrop(self, x, y):
self.dropCounter = self.dropCounter + 1
id = self.dropCounter
drop = CogdoMazeDrop(self, id, x, y)
self.drops[id] = drop
return drop.getDropIval()
def cleanupDrop(self, id):
if id in self.drops.keys():
drop = self.drops[id]
drop.destroy()
del self.drops[id]
def dropHit(self, node, id):
if self.finished:
return
if Globals.DropShakeEnabled:
self.shakeCamera(node, Globals.DropShakeStrength, Globals.DropMaxDistance)
def shakeCamera(self, node, strength, distanceCutoff = 60.0):
distance = self.localPlayer.toon.getDistance(node)
shake = strength * (1 - distance / distanceCutoff)
if shake > 0:
self.localPlayer.shakeCamera(shake)
def handleLocalToonMeetsDrop(self, collEntry):
fcabinet = collEntry.getIntoNodePath()
if fcabinet.getTag('isFalling') == str('False'):
return
if self.localPlayer.state == 'Normal' and not self.localPlayer.invulnerable:
self.distGame.b_toonHitByDrop()
def toonHitByDrop(self, toonId):
player = self.toonId2Player[toonId]
player.hitByDrop()
def handleLocalToonMeetsGagPickup(self, collEntry):
if self.localPlayer.equippedGag != None:
return
into = collEntry.getIntoNodePath()
if into.hasPythonTag('id'):
id = into.getPythonTag('id')
self.distGame.d_sendRequestGagPickUp(id)
return
def hasGag(self, toonId, elapsedTime = 0.0):
player = self.toonId2Player[toonId]
player.equipGag()
def handleLocalToonMeetsWaterCooler(self, collEntry):
if self.localPlayer.equippedGag != None:
return
if self.lastBalloonTimestamp and globalClock.getFrameTime() - self.lastBalloonTimestamp < Globals.BalloonDelay:
return
collNode = collEntry.getIntoNode()
waterCooler = self._collNode2waterCooler[collNode]
self.lastBalloonTimestamp = globalClock.getFrameTime()
self.distGame.d_sendRequestGag(waterCooler.serialNum)
return
def requestUseGag(self, x, y, h):
self.distGame.b_toonUsedGag(x, y, h)
def toonUsedGag(self, toonId, x, y, h, elapsedTime = 0.0):
player = self.toonId2Player[toonId]
heading = h
pos = Point3(x, y, 0)
gag = player.showToonThrowingGag(heading, pos)
if gag is not None:
self.gags.append(gag)
return
def handleToonMeetsGag(self, avId, gag):
self.removeGag(gag)
self.distGame.b_toonHitByGag(avId)
def toonHitByGag(self, toonId, hitToon, elapsedTime = 0.0):
if toonId not in self.toonId2Player.keys() or hitToon not in self.toonId2Player.keys():
return
player = self.toonId2Player[hitToon]
player.hitByGag()
def handleLocalSuitMeetsGag(self, suitType, suitNum, gag):
self.removeGag(gag)
self.localPlayer.hitSuit(suitType)
self.distGame.b_suitHitByGag(suitType, suitNum)
def suitHitByGag(self, toonId, suitType, suitNum, elapsedTime = 0.0):
if suitType == Globals.SuitTypes.Boss:
self.guiMgr.showBossHit(suitNum)
if suitNum in self.suitsById.keys():
suit = self.suitsById[suitNum]
suit.hitByGag()
def removeGag(self, gag):
gag.detachNode()
def toonRevealsDoor(self, toonId):
self._exit.revealed = True
def openDoor(self, timeLeft):
self._audioMgr.playMusic('timeRunningOut')
if not self._exit.isOpen():
self._exit.open()
self.localPlayer.handleOpenDoor(self._exit)
self.guiMgr.showTimer(timeLeft, self._handleTimerExpired)
self.guiMgr.mazeMapGui.showExit()
self.guiMgr.mazeMapGui.revealAll()
def countdown(self, timeLeft):
self.guiMgr.showTimer(timeLeft, self._handleTimerExpired)
def timeAlert(self):
self._audioMgr.playMusic('timeRunningOut')
def _handleTimerExpired(self):
self.localPlayer.request('Done')
def toonEntersDoor(self, toonId):
player = self.toonId2Player[toonId]
self.guiMgr.mazeMapGui.removeToon(player.toon)
self._exit.playerEntersDoor(player)
self.localPlayer.handleToonEntersDoor(toonId, self._exit)
player.request('Done')
def generatePickup(self, x, y):
pickup = CogdoMemo(len(self.pickups), pitch=-90)
self.pickups.append(pickup)
pickup.reparentTo(self.maze.maze)
pickup.setPos(x, y, 1)
pickup.enable()
def handleLocalToonMeetsPickup(self, pickup):
pickup.disable()
self.distGame.d_sendRequestPickUp(pickup.serialNum)
def pickUp(self, toonId, pickupNum, elapsedTime = 0.0):
self.notify.debugCall()
player = self.toonId2Player[toonId]
pickup = self.pickups[pickupNum]
if not pickup.wasPickedUp():
pickup.pickUp(player.toon, elapsedTime)
self.localPlayer.handlePickUp(toonId)
def placePlayer(self, player):
toonIds = self.distGame.getToonIds()
if player.toon.doId not in toonIds:
return
i = toonIds.index(player.toon.doId)
x = int(self.maze.width / 2.0) - int(len(toonIds) / 2.0) + i
y = 2
while not self.maze.isAccessible(x, y):
y += 1
pos = self.maze.tile2world(x, y)
player.toon.setPos(pos[0], pos[1], 0)
self.guiMgr.mazeMapGui.addToon(player.toon, x, y)
def handleLocalToonEntersDoor(self, door):
localToonId = self.localPlayer.toon.doId
if self._exit.isOpen():
self.distGame.d_sendRequestAction(Globals.GameActions.EnterDoor, 0)
else:
if localToonId not in self.toonsThatRevealedDoor:
self.toonsThatRevealedDoor.append(localToonId)
self.localPlayer.handleToonRevealsDoor(localToonId, self._exit)
if not self._exit.revealed:
self.toonRevealsDoor(localToonId)
self.distGame.d_sendRequestAction(Globals.GameActions.RevealDoor, 0)
def handleToonWentSad(self, toonId):
if toonId == self.localPlayer.toon.doId:
for player in self.players:
player.removeGag()
elif toonId in self.toonId2Player.keys():
player = self.toonId2Player[toonId]
player.removeGag()
def handleToonDisconnected(self, toonId):
if toonId == self.localPlayer.toon.doId:
pass
elif toonId in self.toonId2Player.keys():
player = self.toonId2Player[toonId]
self._removePlayer(player)