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 9dd3f5a..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): 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'