diff --git a/etc/toon.dc b/etc/toon.dc index 9e0fef8..5c4ed03 100755 --- a/etc/toon.dc +++ b/etc/toon.dc @@ -618,6 +618,7 @@ dclass DistributedToon : DistributedPlayer { setNeverStartedPartyRefunded(uint64, int8, uint16) ownrecv; setDISLname(string) ram; setDISLid(uint32) ram db airecv; + toggleSleep() ownrecv; }; dclass DistributedCCharBase : DistributedObject { diff --git a/otp/avatar/LocalAvatar.py b/otp/avatar/LocalAvatar.py index 2b689cc..8af6d4b 100644 --- a/otp/avatar/LocalAvatar.py +++ b/otp/avatar/LocalAvatar.py @@ -61,6 +61,7 @@ class LocalAvatar(DistributedAvatar.DistributedAvatar, DistributedSmoothNode.Dis self.soundRun = None self.soundWalk = None self.sleepFlag = 0 + self.noSleep = 0 self.isDisguised = 0 self.movingFlag = 0 self.swimmingFlag = 0 @@ -984,7 +985,7 @@ class LocalAvatar(DistributedAvatar.DistributedAvatar, DistributedSmoothNode.Dis return def gotoSleep(self): - if not self.sleepFlag: + if not self.sleepFlag and not self.noSleep: self.b_setAnimState('Sleep', self.animMultiplier) self.sleepFlag = 1 diff --git a/toontown/battle/BattleCalculatorAI.py b/toontown/battle/BattleCalculatorAI.py index d76ea92..0ed39aa 100644 --- a/toontown/battle/BattleCalculatorAI.py +++ b/toontown/battle/BattleCalculatorAI.py @@ -74,7 +74,8 @@ class BattleCalculatorAI: return (1, 95) else: return (0, 0) - if self.toonsAlwaysHit: + toon = self.battle.getToon(attackIndex) + if self.toonsAlwaysHit or toon.instantKillMode: return (1, 95) elif self.toonsAlwaysMiss: return (0, 0) @@ -529,6 +530,9 @@ class BattleCalculatorAI: if attackLevel == -1 and not atkTrack == FIRE: result = LURE_SUCCEEDED elif atkTrack != TRAP: + toon = self.battle.getToon(toonId) + if atkTrack != HEAL and toon.instantKillMode: + attackDamage = 32767 result = attackDamage if atkTrack == HEAL: if not self.__attackHasHit(attack, suit=0): @@ -747,11 +751,15 @@ class BattleCalculatorAI: toonId = self.toonAtkOrder[attackIndex] attack = self.battle.toonAttacks[toonId] atkTrack = self.__getActualTrack(attack) + TTOStyle = simbase.config.GetBool('want-tto-style-knockback', False) if atkTrack == HEAL or atkTrack == PETSOS: return tgts = self.__createToonTargetList(toonId) for currTgt in tgts: - tgtPos = self.battle.suits.index(currTgt) + if not TTOStyle: + tgtPos = self.battle.activeSuits.index(currTgt) + else: + tgtPos = self.battle.suits.index(currTgt) attackerId = self.toonAtkOrder[attackIndex] attack = self.battle.toonAttacks[attackerId] track = self.__getActualTrack(attack) diff --git a/toontown/battle/MovieLure.py b/toontown/battle/MovieLure.py index 378b0aa..d7dafef 100644 --- a/toontown/battle/MovieLure.py +++ b/toontown/battle/MovieLure.py @@ -98,10 +98,10 @@ def __createFishingPoleMultiTrack(lure, dollar, dollarName): suitTrack.append(Func(suit.loop, 'neutral')) suitTrack.append(Wait(3.5)) suitName = suit.getStyleName() - retardPos, retardHpr = battle.getActorPosHpr(suit) - retardPos.setY(retardPos.getY() + MovieUtil.SUIT_EXTRA_REACH_DISTANCE) + luredPos, luredHpr = battle.getActorPosHpr(suit) + luredPos.setY(luredPos.getY() + MovieUtil.SUIT_EXTRA_REACH_DISTANCE) if suitName in MovieUtil.largeSuits: - moveTrack = lerpSuit(suit, 0.0, reachAnimDuration / 2.5, retardPos, battle, trapProp) + moveTrack = lerpSuit(suit, 0.0, reachAnimDuration / 2.5, luredPos, battle, trapProp) reachTrack = ActorInterval(suit, 'reach', duration=reachAnimDuration) suitTrack.append(Parallel(moveTrack, reachTrack)) else: diff --git a/toontown/building/DistributedToonHallInterior.py b/toontown/building/DistributedToonHallInterior.py index cfbee85..1fb9d16 100644 --- a/toontown/building/DistributedToonHallInterior.py +++ b/toontown/building/DistributedToonHallInterior.py @@ -1,3 +1,4 @@ +from panda3d.core import * from panda3d.toontown import * from toontown.toonbase.ToonBaseGlobal import * from direct.interval.IntervalGlobal import * diff --git a/toontown/golf/GolfHoleBase.py b/toontown/golf/GolfHoleBase.py index 3de8ed8..c2a2106 100644 --- a/toontown/golf/GolfHoleBase.py +++ b/toontown/golf/GolfHoleBase.py @@ -2,6 +2,7 @@ from direct.distributed import DistributedObjectAI from direct.directnotify import DirectNotifyGlobal from toontown.toonbase import ToontownGlobals from panda3d.core import * +from panda3d.ode import OdeTriMeshData, OdeTriMeshGeom, OdeBody, OdeRayGeom from . import DistributedPhysicsWorldAI from direct.fsm.FSM import FSM from toontown.ai.ToonBarrier import * diff --git a/toontown/golf/GolfRewardDialog.py b/toontown/golf/GolfRewardDialog.py index bd90ba8..e02ed2b 100644 --- a/toontown/golf/GolfRewardDialog.py +++ b/toontown/golf/GolfRewardDialog.py @@ -1,5 +1,6 @@ from panda3d.core import * -from toontown.toonbase.ToonBaseGlobal import * +from direct.gui.DirectGui import * +from toontown.toonbase import TTLocalizer, ToontownGlobals from direct.interval.IntervalGlobal import * from direct.task import Task from direct.directnotify import DirectNotifyGlobal diff --git a/toontown/golf/GolfScoreBoard.py b/toontown/golf/GolfScoreBoard.py index 2664b22..684cb94 100644 --- a/toontown/golf/GolfScoreBoard.py +++ b/toontown/golf/GolfScoreBoard.py @@ -1,6 +1,7 @@ from panda3d.core import * -from toontown.toonbase.ToonBaseGlobal import * +from toontown.toonbase import TTLocalizer, ToontownGlobals from direct.directnotify import DirectNotifyGlobal +from direct.gui.DirectGui import * from direct.interval.IntervalGlobal import * from direct.task import Task from math import * diff --git a/toontown/golf/PhysicsWorldBase.py b/toontown/golf/PhysicsWorldBase.py index aa45cfa..d83d36c 100644 --- a/toontown/golf/PhysicsWorldBase.py +++ b/toontown/golf/PhysicsWorldBase.py @@ -2,6 +2,7 @@ from direct.distributed import DistributedObject from direct.directnotify import DirectNotifyGlobal from toontown.toonbase import ToontownGlobals from panda3d.core import * +from panda3d.ode import OdeWorld, OdeSimpleSpace, OdeJointGroup, OdePlaneGeom, OdeBody, OdeMass, OdeSphereGeom, OdeBoxGeom, OdeSliderJoint, OdeTriMeshData, OdeTriMeshGeom, OdeHingeJoint from math import * import math from direct.fsm.FSM import FSM diff --git a/toontown/minigame/DistributedPhotoGame.py b/toontown/minigame/DistributedPhotoGame.py index 7714e7f..99fffa8 100644 --- a/toontown/minigame/DistributedPhotoGame.py +++ b/toontown/minigame/DistributedPhotoGame.py @@ -28,6 +28,8 @@ from toontown.makeatoon import NameGenerator from otp.otpbase import OTPGlobals from toontown.battle import BattleParticles from toontown.minigame import PhotoGameBase +from panda3d.otp import * + WORLD_SCALE = 2.0 FAR_PLANE_DIST = 600 * WORLD_SCALE STAGE_Z_OFFSET = 7.0 @@ -248,6 +250,11 @@ class DistributedPhotoGame(DistributedMinigame, PhotoGameBase.PhotoGameBase): self.removeChildGameFSM(self.gameFSM) del self.gameFSM self.ignoreAll() + + for rayEntry in self.rayArray: + rayEntry[3].remove_node() + del self.rayArray + return def onstage(self): diff --git a/toontown/spellbook/MagicWordIndex.py b/toontown/spellbook/MagicWordIndex.py index a2b7a5f..543e02b 100644 --- a/toontown/spellbook/MagicWordIndex.py +++ b/toontown/spellbook/MagicWordIndex.py @@ -176,7 +176,6 @@ class MagicWord(DirectObject): def handleWord(self, invoker, avId, toon, *args): raise NotImplementedError - class SetHP(MagicWord): aliases = ["hp", "setlaff", "laff"] desc = "Sets the target's current laff." @@ -200,7 +199,6 @@ class SetHP(MagicWord): toon.b_setHp(hp) return "{}'s laff has been set to {}.".format(toon.getName(), hp) - class SetMaxHP(MagicWord): aliases = ["maxhp", "setmaxlaff", "maxlaff"] desc = "Sets the target's max laff." @@ -219,7 +217,6 @@ class SetMaxHP(MagicWord): toon.toonUp(maxhp) return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp) - class ToggleOobe(MagicWord): aliases = ["oobe"] desc = "Toggles the out of body experience mode, which lets you move the camera freely." @@ -232,7 +229,6 @@ class ToggleOobe(MagicWord): base.oobe() return "Oobe mode has been toggled." - class ToggleRun(MagicWord): aliases = ["run"] desc = "Toggles run mode, which gives you a faster running speed." @@ -308,8 +304,6 @@ class Inventory(MagicWord): toon.d_setInventory(toon.inventory.makeNetString()) return ("Zeroing inventory for " + toon.getName() + ".") - - class SetPinkSlips(MagicWord): # this command gives the target toon the specified amount of pink slips # default is 255 @@ -330,7 +324,52 @@ class AbortMinigame(MagicWord): def handleWord(self, invoker, avId, toon, *args): messenger.send("minigameAbort") - return "Requested minigame abort." + return "Requested minigame abort." + +class SkipMiniGolfHole(MagicWord): + aliases = ["skipgolfhole", "skipgolf", "skiphole"] + desc = "Skips the current golf hole." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [] + + def handleWord(self, invoker, avId, toon, *args): + from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI + course = None + for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it. + if isinstance(do, DistributedGolfCourseAI): + if invoker.doId in do.avIdList: + course = do + break + if not course: + return "You aren't in a golf course!" + + if course.isPlayingLastHole(): # If the Toon is on the final hole, calling holeOver() will softlock, so instead we move onto the reward screen. + course.demand('WaitReward') + else: + course.holeOver() + + return "Skipped the current hole." + +class AbortGolfCourse(MagicWord): + aliases = ["abortminigolf", "abortgolf", "abortcourse", "leavegolf", "leavecourse"] + desc = "Aborts the current golf course." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [] + + def handleWord(self, invoker, avId, toon, *args): + from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI + course = None + for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it. + if isinstance(do, DistributedGolfCourseAI): + if invoker.doId in do.avIdList: + course = do + break + if not course: + return "You aren't in a golf course!" + + course.setCourseAbort() + + return "Aborted golf course." class Minigame(MagicWord): aliases = ["mg"] @@ -516,7 +555,7 @@ class BossBattle(MagicWord): if not start: respText += " in Frolic state" - return respText + ", teleporting...", ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0] + return respText + ", teleporting...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0] elif command == "list": # List all the ongoing boss battles. @@ -558,7 +597,7 @@ class BossBattle(MagicWord): return "Index out of range!" boss = AllBossCogs[index] - return "Teleporting to boss battle...", ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0] + return "Teleporting to boss battle...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0] # The following commands needs the invoker to be in a boss battle. @@ -597,13 +636,129 @@ class BossBattle(MagicWord): # The create command is already described when the invoker is not in a battle. These are the commands # they can use INSIDE the battle. - return respText + f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"." + return f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"." def __destroyBoss(self, boss): bossZone = boss.zoneId boss.requestDelete() self.air.deallocateZone(bossZone) +class GlobalTeleport(MagicWord): + aliases = ["globaltp", "tpaccess"] + desc = "Enables teleport access to all zones." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + + def handleWord(self, invoker, avId, toon, *args): + from toontown.toonbase import ToontownGlobals + toon.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll) + toon.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll) + return f"Enabled teleport access to all zones for {toon.getName()}." + +class Teleport(MagicWord): + aliases = ["tp", "goto"] + desc = "Teleport to a specified zone." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("zoneName", str, False, '')] + + def handleWord(self, invoker, avId, toon, *args): + from toontown.hood import ZoneUtil + from toontown.toonbase import ToontownGlobals + zoneName = args[0] + + # Can add stuff like streets to this too if you wanted, but if you do you'll want it to be a valid zone on that street. eg: 2100 is invalid, but any value 2101 to 2156 is fine. + # so if you wanted to add a silly street key, theroetically you could do something like this: 'sillystreet': ToontownGlobals.SillyStreet +1, + zoneName2Id = {'ttc': ToontownGlobals.ToontownCentral, + 'dd': ToontownGlobals.DonaldsDock, + 'dg': ToontownGlobals.DaisyGardens, + 'mml': ToontownGlobals.MinniesMelodyland, + 'tb': ToontownGlobals.TheBrrrgh, + 'ddl': ToontownGlobals.DonaldsDreamland, + 'gs': ToontownGlobals.GoofySpeedway, + 'oz': ToontownGlobals.OutdoorZone, + 'aa': ToontownGlobals.OutdoorZone, + 'gz': ToontownGlobals.GolfZone, + 'sbhq': ToontownGlobals.SellbotHQ, + 'factory': ToontownGlobals.SellbotFactoryExt, + 'cbhq': ToontownGlobals.CashbotHQ, + 'lbhq': ToontownGlobals.LawbotHQ, + 'bbhq': ToontownGlobals.BossbotHQ} + + try: + zone = zoneName2Id[zoneName] + except KeyError: + return "Unknown zone name!" + + return f"Requested to teleport {toon.getName()} to zone {zone}.", toon.doId, [ZoneUtil.getBranchLoaderName(zone), ZoneUtil.getToonWhereName(zone), "", ZoneUtil.getHoodId(zone), zone, 0] + +class ToggleSleep(MagicWord): + aliases = ["sleep", "nosleep", "neversleep", "togglesleeping", "insomnia"] + desc = "Toggles sleeping for the target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + + def handleWord(self, invoker, avId, toon, *args): + toon.d_toggleSleep() + return f"Toggled sleeping for {toon.getName()}." + +class ToggleImmortal(MagicWord): + aliases = ["immortal", "invincible", "invulnerable"] + desc = "Toggle immortal mode. This makes the Toon immune to damage." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + + def handleWord(self, invoker, avId, toon, *args): + toon.setImmortalMode(not toon.immortalMode) + return f"Toggled immortal mode for {toon.getName()}" + +class ToggleGhost(MagicWord): + aliases = ["ghost", "invisible", "spy"] + desc = "Toggle ghost mode." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + + def handleWord(self, invoker, avId, toon, *args): + # 1 is for the attic, 2 enables you to see yourself other ghost toons. 0 is off. + toon.b_setGhostMode(2 if not toon.ghostMode else 0) # As it's primarily for moderation purposes, we set it to 2 here, or 0 if it's already on. + return f"Toggled ghost mode for {toon.getName()}" + +class SetGM(MagicWord): + aliases = ["icon", "seticon", "gm", "gmicon", "setgmicon"] + desc = "Sets the GM icon on the target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("iconRequest", int, False, 0),] + + def handleWord(self, invoker, avId, toon, *args): + from toontown.toonbase import TTLocalizer + + iconRequest = args[0] + if iconRequest > len(TTLocalizer.GM_NAMES) or iconRequest < 0: + return "Invalid GM icon ID!" + + toon.b_setGM(0) # Reset it first, otherwise the Toon keeps the old icon, but the name still changes. + toon.b_setGM(iconRequest) + return f"GM icon set to {iconRequest} for {toon.getName()}" + +class SetMaxCarry(MagicWord): + aliases = ["gagpouch", "pouch", "gagcapacity"] + desc = "Set a Toon's gag pouch size." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("pouchSize", int, True)] + + def handleWord(self, invoker, avId, toon, *args): + pouchSize = args[0] + + if pouchSize > 255 or pouchSize < 0: + return "Specified pouch size must be between 1 and 255." + + toon.b_setMaxCarry(pouchSize) + return f"Set gag pouch size to {pouchSize} for {toon.getName()}" + +class ToggleInstantKill(MagicWord): + aliases = ["instantkill", "instakill"] + desc = "Toggle the ability to instantly kill a Cog with any gag." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + + def handleWord(self, invoker, avId, toon, *args): + toon.setInstantKillMode(not toon.instantKillMode) + return f"Toggled instant-kill mode for {toon.getName()}" + class Fireworks(MagicWord): aliases = ["firework"] desc = "Starts a firework show." diff --git a/toontown/spellbook/ToontownMagicWordManager.py b/toontown/spellbook/ToontownMagicWordManager.py index 9a53d36..d8a6c2f 100644 --- a/toontown/spellbook/ToontownMagicWordManager.py +++ b/toontown/spellbook/ToontownMagicWordManager.py @@ -275,11 +275,14 @@ class ToontownMagicWordManager(DistributedObject.DistributedObject): hoodId = place.loader.hood.id if avId == 0: avId = -1 - place.fsm.forceTransition('teleportOut', - [{"loader": loaderId, - "where": whereId, - "how": how, - "hoodId": hoodId, - "zoneId": zoneId, - "shardId": None, - "avId": avId}]) + try: + place.fsm.forceTransition('teleportOut', + [{"loader": loaderId, + "where": whereId, + "how": how, + "hoodId": hoodId, + "zoneId": zoneId, + "shardId": None, + "avId": avId}]) + except Exception: # Most likely cause is the place the avatar is in has no teleportOut state, for example, boss lobbies. + place.fsm.request('DFAReject') # We have to do this, or the avatar will be stuck. diff --git a/toontown/toon/DistributedToon.py b/toontown/toon/DistributedToon.py index 0602211..3a52f75 100644 --- a/toontown/toon/DistributedToon.py +++ b/toontown/toon/DistributedToon.py @@ -2528,7 +2528,7 @@ class DistributedToon(DistributedPlayer.DistributedPlayer, Toon.Toon, Distribute self._handleGMName() def _handleGMName(self): - name = self.name + name = self._name self.setDisplayName(name) if self._isGM: self.setNametagStyle(5) @@ -2589,3 +2589,6 @@ class DistributedToon(DistributedPlayer.DistributedPlayer, Toon.Toon, Distribute def getTransitioning(self): return self.transitioning + + def toggleSleep(self): + base.localAvatar.noSleep = not base.localAvatar.noSleep \ No newline at end of file diff --git a/toontown/toon/DistributedToonAI.py b/toontown/toon/DistributedToonAI.py index cf78958..b5489fe 100644 --- a/toontown/toon/DistributedToonAI.py +++ b/toontown/toon/DistributedToonAI.py @@ -155,6 +155,7 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo self.savedCheesyExpireTime = 0 self.ghostMode = 0 self.immortalMode = 0 + self.instantKillMode = 0 self.numPies = 0 self.pieType = 0 self._isGM = False @@ -2375,6 +2376,9 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo def setImmortalMode(self, flag): self.immortalMode = flag + def setInstantKillMode(self, flag): + self.instantKillMode = flag + def b_setSpeedChatStyleIndex(self, index): self.setSpeedChatStyleIndex(index) self.d_setSpeedChatStyleIndex(index) @@ -4129,6 +4133,9 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo zoneId]) self.air.send(dg) + def d_toggleSleep(self): + self.sendUpdate('toggleSleep', []) + @staticmethod def staticGetLogicalZoneChangeAllEvent(): return 'DOLogicalChangeZone-all' diff --git a/toontown/toon/Toon.py b/toontown/toon/Toon.py index 8cc88c1..75b616b 100644 --- a/toontown/toon/Toon.py +++ b/toontown/toon/Toon.py @@ -860,6 +860,9 @@ class Toon(Avatar.Avatar, ToonHead): self.resetHeight() self.setupToonNodes() self.generateBackpack() + hands = self.getRightHands() + for bookActor, hand in zip(self.__bookActors, hands): + bookActor.reparentTo(hand) def generateToonHead(self, copy = 1): headHeight = ToonHead.generateToonHead(self, copy, self.style, ('1000', '500', '250'))