import copy
from direct.controls.GravityWalker import GravityWalker
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObject
from direct.distributed import DistributedSmoothNode
from direct.distributed.ClockDelta import *
from direct.distributed.MsgTypes import *
from direct.fsm import ClassicFSM
from direct.interval.IntervalGlobal import Sequence, Wait, Func, Parallel, SoundInterval
from direct.showbase import PythonUtil
from direct.task.Task import Task
import operator
from panda3d.core import *
import random
import time

import Experience
import InventoryNew
import TTEmote
import Toon
from otp.ai.MagicWordGlobal import *
from otp.avatar import Avatar, DistributedAvatar
from otp.avatar import DistributedPlayer
from otp.chat import TalkAssistant, ChatUtil
from otp.otpbase import OTPGlobals
from otp.otpbase import OTPLocalizer
from otp.speedchat import SCDecoders
from toontown.catalog import CatalogItem
from toontown.catalog import CatalogItemList
from toontown.chat import ResistanceChat
from toontown.chat import ToonChatGarbler
from otp.nametag.NametagConstants import *
from otp.margins.WhisperPopup import *
from toontown.coghq import CogDisguiseGlobals
from toontown.distributed import DelayDelete
from toontown.distributed import DelayDelete
from toontown.distributed.DelayDeletable import DelayDeletable
from toontown.effects.ScavengerHuntEffects import *
from toontown.estate import DistributedGagTree
from toontown.estate import FlowerBasket
from toontown.estate import FlowerCollection
from toontown.estate import GardenDropGame
from toontown.estate import GardenGlobals
from toontown.fishing import FishCollection
from toontown.fishing import FishTank
from toontown.friends import FriendHandle
from toontown.golf import GolfGlobals
from toontown.hood import ZoneUtil
from otp.nametag import NametagGroup
from otp.nametag.NametagGroup import *
from toontown.parties import PartyGlobals
from toontown.parties.InviteInfo import InviteInfo
from toontown.parties.PartyGlobals import InviteStatus, PartyStatus
from toontown.parties.PartyInfo import PartyInfo
from toontown.parties.PartyReplyInfo import PartyReplyInfoBase
from toontown.parties.SimpleMailBase import SimpleMailBase
from toontown.shtiker.OptionsPage import speedChatStyles
from toontown.speedchat import TTSCDecoders
from toontown.suit import SuitDNA
from toontown.toonbase import TTLocalizer
from toontown.toonbase import ToontownGlobals
from toontown.battle import BattleParticles

if base.wantKarts:
    from toontown.racing.KartDNA import *

class DistributedToon(DistributedPlayer.DistributedPlayer, Toon.Toon, DistributedSmoothNode.DistributedSmoothNode, DelayDeletable):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedToon')
    partyNotify = DirectNotifyGlobal.directNotify.newCategory('DistributedToon_Party')
    chatGarbler = ToonChatGarbler.ToonChatGarbler()

    def __init__(self, cr, bFake = False):
        try:
            self.DistributedToon_initialized
            return
        except:
            self.DistributedToon_initialized = 1

        DistributedPlayer.DistributedPlayer.__init__(self, cr)
        Toon.Toon.__init__(self)
        DistributedSmoothNode.DistributedSmoothNode.__init__(self, cr)
        self.bFake = bFake
        self.kart = None
        self.trophyScore = 0
        self.trophyStar = None
        self.trophyStarSpeed = 0
        self.NPCFriendsDict = {}
        self.earnedExperience = None
        self.track = None
        self.effect = None
        self.maxCarry = 0
        self.disguisePageFlag = 0
        self.sosPageFlag = 0
        self.disguisePage = None
        self.sosPage = None
        self.gardenPage = None
        self.emoteAccess = [0] * 27
        self.cogTypes = [0, 0, 0, 0]
        self.cogLevels = [0, 0, 0, 0]
        self.cogParts = [0, 0, 0, 0]
        self.cogMerits = [0, 0, 0, 0]
        self.savedCheesyEffect = ToontownGlobals.CENormal
        self.savedCheesyHoodId = 0
        self.savedCheesyExpireTime = 0
        if hasattr(base, 'wantPets') and base.wantPets:
            self.petTrickPhrases = []
        self.customMessages = []
        self.resistanceMessages = []
        self.cogSummonsEarned = []
        self.catalogNotify = ToontownGlobals.NoItems
        self.mailboxNotify = ToontownGlobals.NoItems
        self.simpleMailNotify = ToontownGlobals.NoItems
        self.inviteMailNotify = ToontownGlobals.NoItems
        self.catalogScheduleCurrentWeek = 0
        self.catalogScheduleNextTime = 0
        self.monthlyCatalog = CatalogItemList.CatalogItemList()
        self.weeklyCatalog = CatalogItemList.CatalogItemList()
        self.backCatalog = CatalogItemList.CatalogItemList()
        self.onOrder = CatalogItemList.CatalogItemList(store=CatalogItem.Customization | CatalogItem.DeliveryDate)
        self.onGiftOrder = CatalogItemList.CatalogItemList(store=CatalogItem.Customization | CatalogItem.DeliveryDate)
        self.mailboxContents = CatalogItemList.CatalogItemList(store=CatalogItem.Customization)
        self.awardMailboxContents = CatalogItemList.CatalogItemList(store=CatalogItem.Customization)
        self.onAwardOrder = CatalogItemList.CatalogItemList(store=CatalogItem.Customization | CatalogItem.DeliveryDate)
        self.splash = None
        self.tossTrack = None
        self.pieTracks = {}
        self.splatTracks = {}
        self.lastTossedPie = 0
        self.clothesTopsList = []
        self.clothesBottomsList = []
        self.hatList = []
        self.glassesList = []
        self.backpackList = []
        self.shoesList = []
        self.tunnelTrack = None
        self.tunnelPivotPos = [-14, -6, 0]
        self.tunnelCenterOffset = 9.0
        self.tunnelCenterInfluence = 0.6
        self.pivotAngle = 90 + 45
        self.houseId = 0
        self.money = 0
        self.bankMoney = 0
        self.maxMoney = 0
        self.maxBankMoney = 0
        self.emblems = [0, 0]
        self.petId = 0
        self.petTutorialDone = False
        self.fishBingoTutorialDone = False
        self.fishBingoMarkTutorialDone = False
        self.accessories = []
        if base.wantKarts:
            self.kartDNA = [-1] * getNumFields()
        self.flowerCollection = None
        self.shovel = 0
        self.shovelSkill = 0
        self.shovelModel = None
        self.wateringCan = 0
        self.wateringCanSkill = 0
        self.wateringCanModel = None
        self.gardenSpecials = []
        self.unlimitedSwing = 0
        self.soundSequenceList = []
        self.boardingParty = None
        self.__currentDialogue = None
        self.mail = None
        self.invites = []
        self.hostedParties = []
        self.partiesInvitedTo = []
        self.partyReplyInfoBases = []
        self.buffs = []
        self.redeemedCodes = []
        self.ignored = []
        self.reported = []
        self.trueFriends = []
        self.specialInventory = [0, 0, 0, 0, 0]

    def disable(self):
        for soundSequence in self.soundSequenceList:
            soundSequence.finish()

        self.soundSequenceList = []
        if self.boardingParty:
            self.boardingParty.demandDrop()
            self.boardingParty = None
        self.ignore('clientCleanup')
        self.stopAnimations()
        self.clearCheesyEffect()
        self.stopBlink()
        self.stopSmooth()
        self.stopLookAroundNow()
        self.setGhostMode(0)
        if self.track != None:
            self.track.finish()
            DelayDelete.cleanupDelayDeletes(self.track)
            self.track = None
        if self.effect != None:
            self.effect.destroy()
            self.effect = None
        if self.splash != None:
            self.splash.destroy()
            self.splash = None
        if self.emote != None:
            self.emote.finish()
            self.emote = None
        self.cleanupPies()
        if self.isDisguised:
            self.takeOffSuit()
        if self.tunnelTrack:
            self.tunnelTrack.finish()
            self.tunnelTrack = None
        self.setTrophyScore(0)
        if self.doId in self.cr.toons:
            del self.cr.toons[self.doId]
        DistributedPlayer.DistributedPlayer.disable(self)

    def delete(self):
        try:
            self.DistributedToon_deleted
        except:
            self.DistributedToon_deleted = 1
            DistributedPlayer.DistributedPlayer.delete(self)
            Toon.Toon.delete(self)
            DistributedSmoothNode.DistributedSmoothNode.delete(self)

    def generate(self):
        DistributedPlayer.DistributedPlayer.generate(self)
        DistributedSmoothNode.DistributedSmoothNode.generate(self)
        self.cr.toons[self.doId] = self
        if base.cr.trophyManager != None:
            base.cr.trophyManager.d_requestTrophyScore()
        self.startBlink()
        self.startSmooth()
        self.accept('clientCleanup', self._handleClientCleanup)

    def announceGenerate(self):
        DistributedPlayer.DistributedPlayer.announceGenerate(self)
        if self.animFSM.getCurrentState().getName() == 'off':
            self.setAnimState('neutral')

    def _handleClientCleanup(self):
        if self.track != None:
            DelayDelete.cleanupDelayDeletes(self.track)

    def setDNAString(self, dnaString):
        Toon.Toon.setDNAString(self, dnaString)

    def setAdminAccess(self, access):
        DistributedPlayer.DistributedPlayer.setAdminAccess(self, access)
        self.removeGMIcon()
        if self.isAdmin():
            self.setGMIcon(access)

    def setDNA(self, dna):
        oldHat = self.getHat()
        oldGlasses = self.getGlasses()
        oldBackpack = self.getBackpack()
        oldShoes = self.getShoes()
        self.setHat(0, 0, 0)
        self.setGlasses(0, 0, 0)
        self.setBackpack(0, 0, 0)
        self.setShoes(0, 0, 0)
        Toon.Toon.setDNA(self, dna)
        self.setHat(*oldHat)
        self.setGlasses(*oldGlasses)
        self.setBackpack(*oldBackpack)
        self.setShoes(*oldShoes)

    def setHat(self, idx, textureIdx, colorIdx):
        Toon.Toon.setHat(self, idx, textureIdx, colorIdx)

    def setGlasses(self, idx, textureIdx, colorIdx):
        Toon.Toon.setGlasses(self, idx, textureIdx, colorIdx)

    def setBackpack(self, idx, textureIdx, colorIdx):
        Toon.Toon.setBackpack(self, idx, textureIdx, colorIdx)

    def setShoes(self, idx, textureIdx, colorIdx):
        Toon.Toon.setShoes(self, idx, textureIdx, colorIdx)

    def setExperience(self, experience):
        self.experience = Experience.Experience(experience, self)
        if self.inventory:
            self.inventory.updateGUI()

    def setInventory(self, inventoryNetString):
        if not self.inventory:
            self.inventory = InventoryNew.InventoryNew(self, inventoryNetString)
        self.inventory.updateInvString(inventoryNetString)

    def setLastHood(self, lastHood):
        self.lastHood = lastHood

    def setBattleId(self, battleId):
        self.battleId = battleId
        messenger.send('ToonBattleIdUpdate', [self.doId])

    def b_setSCToontask(self, taskId, toNpcId, toonProgress, msgIndex):
        self.setSCToontask(taskId, toNpcId, toonProgress, msgIndex)
        self.d_setSCToontask(taskId, toNpcId, toonProgress, msgIndex)
        return None

    def d_setSCToontask(self, taskId, toNpcId, toonProgress, msgIndex):
        messenger.send('wakeup')
        self.sendUpdate('setSCToontask', [taskId,
         toNpcId,
         toonProgress,
         msgIndex])

    def setSCToontask(self, taskId, toNpcId, toonProgress, msgIndex):
        if base.localAvatar.isIgnored(self.doId):
            return
        chatString = TTSCDecoders.decodeTTSCToontaskMsg(taskId, toNpcId, toonProgress, msgIndex)
        if chatString:
            self.setChatAbsolute(chatString, CFSpeech | CFQuicktalker | CFTimeout)

    def sendLogSuspiciousEvent(self, msg):
        localAvatar.sendUpdate('logSuspiciousEvent', ['%s for %s' % (msg, self.doId)])

    def d_reqSCResistance(self, msgIndex):
        messenger.send('wakeup')
        nearbyPlayers = self.getNearbyPlayers(ResistanceChat.EFFECT_RADIUS)
        self.sendUpdate('reqSCResistance', [msgIndex, nearbyPlayers])

    def getNearbyPlayers(self, radius, includeSelf = True):
        nearbyToons = []
        toonIds = self.cr.getObjectsOfExactClass(DistributedToon)
        for toonId, toon in toonIds.items():
            if toon is not self:
                dist = toon.getDistance(self)
                if dist < radius:
                    nearbyToons.append(toonId)

        if includeSelf:
            nearbyToons.append(self.doId)
        return nearbyToons

    def setSCResistance(self, msgIndex, nearbyToons = []):
        chatString = TTSCDecoders.decodeTTSCResistanceMsg(msgIndex)
        if chatString:
            self.setChatAbsolute(chatString, CFSpeech | CFTimeout)
        ResistanceChat.doEffect(msgIndex, self, nearbyToons)

    def d_battleSOS(self, sendToId):
        self.cr.ttsFriendsManager.d_battleSOS(sendToId)

    def battleSOS(self, requesterId):
        avatar = base.cr.identifyAvatar(requesterId)

        if isinstance(avatar, (DistributedToon, FriendHandle.FriendHandle)):
            self.setSystemMessage(requesterId,
                TTLocalizer.MovieSOSWhisperHelp % avatar.getName(),
                whisperType=WTBattleSOS
            )
        elif avatar:
            self.notify.warning('got battleSOS from non-toon %s' % requesterId)

    def getDialogueArray(self, *args):
        if hasattr(self, 'animalSound'):
            dialogueArrays = [
                Toon.DogDialogueArray,
                Toon.CatDialogueArray,
                Toon.HorseDialogueArray,
                Toon.MouseDialogueArray,
                Toon.RabbitDialogueArray,
                Toon.DuckDialogueArray,
                Toon.MonkeyDialogueArray,
                Toon.BearDialogueArray,
                Toon.PigDialogueArray,
            ]
            return dialogueArrays[self.animalSound]
        return Toon.Toon.getDialogueArray(self, *args)

    def setDefaultShard(self, shard):
        self.defaultShard = shard

    def setDefaultZone(self, zoneId):
        if not ZoneUtil.getCanonicalHoodId(zoneId) in ToontownGlobals.hoodNameMap:
            self.defaultZone = ToontownGlobals.ToontownCentral
            return
        if ZoneUtil.getCanonicalHoodId(zoneId) == ToontownGlobals.FunnyFarm:
            self.defaultZone = ToontownGlobals.ToontownCentral
            return
        if self.getHp() <= 0 and zoneId in ToontownGlobals.HQToSafezone:
            self.defaultZone = ToontownGlobals.HQToSafezone[zoneId]
            return
        self.defaultZone = zoneId

    def __starSpin1(self, task):
        now = globalClock.getFrameTime()
        r = now * 90 % 360.0
        self.trophyStar1.setH(r)
        return Task.cont

    def isAvFriend(self, avId):
        return base.cr.isFriend(avId)

    def setTalkWhisper(self, avId, chat):
        if not base.cr.chatAgent.verifyMessage(chat):
            return
        if not localAvatar.acceptingNonFriendWhispers:
            if not self.isAvFriend(avId):
                return
        if base.localAvatar.isIgnored(avId):
            return
        if base.localAvatar.sleepFlag == 1:
            if not base.cr.identifyAvatar(avId) == base.localAvatar:
                base.cr.ttsFriendsManager.d_sleepAutoReply(avId)
        if base.whiteList:
            chat = base.whiteList.processThroughAll(chat, self.chatGarbler)
        self.displayTalkWhisper(avId, chat)

    def setSleepAutoReply(self, fromId):
        pass

    def _isValidWhisperSource(self, source):
        return isinstance(source, (DistributedToon, FriendHandle.FriendHandle))

    def setWhisperSCEmoteFrom(self, fromId, emoteId):
        handle = base.cr.identifyAvatar(fromId)
        if handle == None:
            return
        if not self._isValidWhisperSource(handle):
            self.notify.warning('setWhisperSCEmoteFrom non-toon %s' % fromId)
            return
        if not localAvatar.acceptingNonFriendWhispers:
            if not self.isAvFriend(fromId):
                return
        if base.localAvatar.isIgnored(fromId):
            return
        if base.localAvatar.sleepFlag == 1:
            if not handle == base.localAvatar:
                base.cr.ttsFriendsManager.d_sleepAutoReply(fromId)
        chatString = SCDecoders.decodeSCEmoteWhisperMsg(emoteId, handle.getName())
        if chatString:
            self.displayWhisper(fromId, chatString, WTEmote)
            base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_EMOTE, emoteId, fromId)
        return

    def setWhisperSCFrom(self, fromId, msgIndex):
        handle = base.cr.identifyAvatar(fromId)
        if handle == None:
            return
        if not self._isValidWhisperSource(handle):
            self.notify.warning('setWhisperSCFrom non-toon %s' % fromId)
            return
        if not localAvatar.acceptingNonFriendWhispers:
            if not self.isAvFriend(fromId):
                return
        if base.localAvatar.isIgnored(fromId):
            return
        if base.localAvatar.sleepFlag == 1:
            if not handle == base.localAvatar:
                base.cr.ttsFriendsManager.d_sleepAutoReply(fromId)
        chatString = SCDecoders.decodeSCStaticTextMsg(msgIndex)
        if chatString:
            self.displayWhisper(fromId, chatString, WTNormal)
            base.talkAssistant.receiveAvatarWhisperSpeedChat(TalkAssistant.SPEEDCHAT_NORMAL, msgIndex, fromId)
        return

    def setWhisperSCCustomFrom(self, fromId, msgIndex):
        handle = base.cr.identifyAvatar(fromId)
        if handle == None:
            return
        if not localAvatar.acceptingNonFriendWhispers:
            if not self.isAvFriend(fromId):
                return
        return DistributedPlayer.DistributedPlayer.setWhisperSCCustomFrom(self, fromId, msgIndex)

    def whisperSCToontaskTo(self, taskId, toNpcId, toonProgress, msgIndex, sendToId):
        messenger.send('wakeup')

        base.cr.ttsFriendsManager.d_whisperSCToontaskTo(sendToId, taskId,
            toNpcId, toonProgress, msgIndex
        )

    def setWhisperSCToontaskFrom(self, fromId, taskId, toNpcId, toonProgress, msgIndex):
        sender = base.cr.identifyAvatar(fromId)
        if sender is None:
            return

        if not localAvatar.acceptingNonFriendWhispers:
            if not self.isAvFriend(fromId):
                return

        if base.localAvatar.isIgnored(fromId):
            return

        chatString = TTSCDecoders.decodeTTSCToontaskMsg(taskId, toNpcId, toonProgress, msgIndex)
        if chatString:
            self.displayWhisper(fromId, chatString, WTNormal)

    def getNPCFriendsDict(self):
        return self.NPCFriendsDict

    def setNPCFriendsDict(self, NPCFriendsList):
        NPCFriendsDict = {}
        for friendPair in NPCFriendsList:
            NPCFriendsDict[friendPair[0]] = friendPair[1]

        self.NPCFriendsDict = NPCFriendsDict

    def setHatList(self, clothesList):
        self.hatList = clothesList

    def getHatList(self):
        return self.hatList

    def setGlassesList(self, clothesList):
        self.glassesList = clothesList

    def getGlassesList(self):
        return self.glassesList

    def setBackpackList(self, clothesList):
        self.backpackList = clothesList

    def getBackpackList(self):
        return self.backpackList

    def setShoesList(self, clothesList):
        self.shoesList = clothesList

    def getShoesList(self):
        return self.shoesList

    def isTrunkFull(self, extraAccessories = 0):
        numAccessories = (len(self.hatList) + len(self.glassesList) + len(self.backpackList) + len(self.shoesList)) / 3
        return numAccessories + extraAccessories >= ToontownGlobals.MaxAccessories

    def setMaxClothes(self, max):
        self.maxClothes = max

    def getMaxClothes(self):
        return self.maxClothes

    def getClothesTopsList(self):
        return self.clothesTopsList

    def setClothesTopsList(self, clothesList):
        self.clothesTopsList = clothesList

    def getClothesBottomsList(self):
        return self.clothesBottomsList

    def setClothesBottomsList(self, clothesList):
        self.clothesBottomsList = clothesList

    def catalogGenClothes(self, avId):
        if avId == self.doId:
            self.generateToonClothes()
            self.loop('neutral')

    def catalogGenAccessories(self, avId):
        if avId == self.doId:
            self.generateToonAccessories()
            self.loop('neutral')

    def isClosetFull(self, extraClothes = 0):
        numClothes = len(self.clothesTopsList) / 4 + len(self.clothesBottomsList) / 2
        return numClothes + extraClothes >= self.maxClothes

    def setMaxHp(self, hitPoints):
        DistributedPlayer.DistributedPlayer.setMaxHp(self, hitPoints)
        if self.inventory:
            self.inventory.updateGUI()

    def died(self):
        messenger.send(self.uniqueName('died'))
        if self.isLocal():
            target_sz = ZoneUtil.getSafeZoneId(self.defaultZone)
            place = self.cr.playGame.getPlace()
            if place and place.fsm:
                place.fsm.request('died', [{'loader': ZoneUtil.getLoaderName(target_sz),
                  'where': ZoneUtil.getWhereName(target_sz, 1),
                  'how': 'teleportIn',
                  'hoodId': target_sz,
                  'zoneId': target_sz,
                  'shardId': None,
                  'avId': -1,
                  'battle': 1}])
        return

    def setHoodsVisited(self, hoods):
        self.hoodsVisited = hoods
        if ToontownGlobals.SellbotHQ in hoods or ToontownGlobals.CashbotHQ in hoods or ToontownGlobals.LawbotHQ in hoods or ToontownGlobals.BossbotHQ in hoods:
            self.setDisguisePageFlag(1)

    def wrtReparentTo(self, parent):
        DistributedSmoothNode.DistributedSmoothNode.wrtReparentTo(self, parent)

    def setTutorialAck(self, tutorialAck):
        self.tutorialAck = tutorialAck

    def setEarnedExperience(self, earnedExp):
        self.earnedExperience = earnedExp

    def b_setTunnelIn(self, endX, tunnelOrigin):
        timestamp = globalClockDelta.getFrameNetworkTime()
        pos = tunnelOrigin.getPos(render)
        h = tunnelOrigin.getH(render)
        self.setTunnelIn(timestamp, endX, pos[0], pos[1], pos[2], h)
        self.d_setTunnelIn(timestamp, endX, pos[0], pos[1], pos[2], h)

    def d_setTunnelIn(self, timestamp, endX, x, y, z, h):
        self.sendUpdate('setTunnelIn', [timestamp,
         endX,
         x,
         y,
         z,
         h])

    def setTunnelIn(self, timestamp, endX, x, y, z, h):
        t = globalClockDelta.networkToLocalTime(timestamp)
        self.handleTunnelIn(t, endX, x, y, z, h)

    def getTunnelInToonTrack(self, endX, tunnelOrigin):
        pivotNode = tunnelOrigin.attachNewNode(self.uniqueName('pivotNode'))
        pivotNode.setPos(*self.tunnelPivotPos)
        pivotNode.setHpr(0, 0, 0)
        pivotY = pivotNode.getY(tunnelOrigin)
        endY = 5.0
        straightLerpDur = abs(endY - pivotY) / ToontownGlobals.ToonForwardSpeed
        pivotDur = 2.0
        pivotLerpDur = pivotDur * (90.0 / self.pivotAngle)
        self.reparentTo(pivotNode)
        self.setPos(0, 0, 0)
        self.setX(tunnelOrigin, endX)
        targetX = self.getX()
        self.setX(self.tunnelCenterOffset + (targetX - self.tunnelCenterOffset) * (1.0 - self.tunnelCenterInfluence))
        self.setHpr(tunnelOrigin, 0, 0, 0)
        pivotNode.setH(-self.pivotAngle)
        return Sequence(Wait(0.8), Parallel(LerpHprInterval(pivotNode, pivotDur, hpr=Point3(0, 0, 0), name=self.uniqueName('tunnelInPivot')), Sequence(Wait(pivotDur - pivotLerpDur), LerpPosInterval(self, pivotLerpDur, pos=Point3(targetX, 0, 0), name=self.uniqueName('tunnelInPivotLerpPos')))), Func(self.wrtReparentTo, render), Func(pivotNode.removeNode), LerpPosInterval(self, straightLerpDur, pos=Point3(endX, endY, 0.1), other=tunnelOrigin, name=self.uniqueName('tunnelInStraightLerp')))

    def handleTunnelIn(self, startTime, endX, x, y, z, h):
        self.stopSmooth()
        tunnelOrigin = render.attachNewNode('tunnelOrigin')
        tunnelOrigin.setPosHpr(x, y, z, h, 0, 0)
        if self.tunnelTrack:
            self.tunnelTrack.finish()
        self.tunnelTrack = Sequence(self.getTunnelInToonTrack(endX, tunnelOrigin), Func(tunnelOrigin.removeNode), Func(self.startSmooth))
        tOffset = globalClock.getFrameTime() - (startTime + self.smoother.getDelay())
        if tOffset < 0.0:
            self.tunnelTrack = Sequence(Wait(-tOffset), self.tunnelTrack)
            self.tunnelTrack.start()
        else:
            self.tunnelTrack.start(tOffset)

    def b_setTunnelOut(self, startX, startY, tunnelOrigin):
        timestamp = globalClockDelta.getFrameNetworkTime()
        pos = tunnelOrigin.getPos(render)
        h = tunnelOrigin.getH(render)
        self.setTunnelOut(timestamp, startX, startY, pos[0], pos[1], pos[2], h)
        self.d_setTunnelOut(timestamp, startX, startY, pos[0], pos[1], pos[2], h)

    def d_setTunnelOut(self, timestamp, startX, startY, x, y, z, h):
        self.sendUpdate('setTunnelOut', [timestamp,
         startX,
         startY,
         x,
         y,
         z,
         h])

    def setTunnelOut(self, timestamp, startX, startY, x, y, z, h):
        t = globalClockDelta.networkToLocalTime(timestamp)
        self.handleTunnelOut(t, startX, startY, x, y, z, h)

    def getTunnelOutToonTrack(self, startX, startY, tunnelOrigin):
        startPos = self.getPos(tunnelOrigin)
        startHpr = self.getHpr(tunnelOrigin)
        reducedAvH = PythonUtil.fitDestAngle2Src(startHpr[0], 180)
        pivotNode = tunnelOrigin.attachNewNode(self.uniqueName('pivotNode'))
        pivotNode.setPos(*self.tunnelPivotPos)
        pivotNode.setHpr(0, 0, 0)
        pivotY = pivotNode.getY(tunnelOrigin)
        straightLerpDur = abs(startY - pivotY) / ToontownGlobals.ToonForwardSpeed
        pivotDur = 2.0
        pivotLerpDur = pivotDur * (90.0 / self.pivotAngle)

        def getTargetPos(self = self):
            pos = self.getPos()
            return Point3(self.tunnelCenterOffset + (pos[0] - self.tunnelCenterOffset) * (1.0 - self.tunnelCenterInfluence), pos[1], pos[2])

        return Sequence(Parallel(LerpPosInterval(self, straightLerpDur, pos=Point3(startX, pivotY, 0.1), startPos=startPos, other=tunnelOrigin, name=self.uniqueName('tunnelOutStraightLerp')), LerpHprInterval(self, straightLerpDur * 0.8, hpr=Point3(reducedAvH, 0, 0), startHpr=startHpr, other=tunnelOrigin, name=self.uniqueName('tunnelOutStraightLerpHpr'))), Func(self.wrtReparentTo, pivotNode), Parallel(LerpHprInterval(pivotNode, pivotDur, hpr=Point3(-self.pivotAngle, 0, 0), name=self.uniqueName('tunnelOutPivot')), LerpPosInterval(self, pivotLerpDur, pos=getTargetPos, name=self.uniqueName('tunnelOutPivotLerpPos'))), Func(self.wrtReparentTo, render), Func(pivotNode.removeNode))

    def handleTunnelOut(self, startTime, startX, startY, x, y, z, h):
        tunnelOrigin = render.attachNewNode('tunnelOrigin')
        tunnelOrigin.setPosHpr(x, y, z, h, 0, 0)
        if self.tunnelTrack:
            self.tunnelTrack.finish()
        self.tunnelTrack = Sequence(Func(self.stopSmooth), self.getTunnelOutToonTrack(startX, startY, tunnelOrigin), Func(self.detachNode), Func(tunnelOrigin.removeNode))
        tOffset = globalClock.getFrameTime() - (startTime + self.smoother.getDelay())
        if tOffset < 0.0:
            self.tunnelTrack = Sequence(Wait(-tOffset), self.tunnelTrack)
            self.tunnelTrack.start()
        else:
            self.tunnelTrack.start(tOffset)

    def enterTeleportOut(self, *args, **kw):
        Toon.Toon.enterTeleportOut(self, *args, **kw)
        if self.track:
            self.track.delayDelete = DelayDelete.DelayDelete(self, 'enterTeleportOut')

    def exitTeleportOut(self):
        if self.track != None:
            DelayDelete.cleanupDelayDeletes(self.track)
        Toon.Toon.exitTeleportOut(self)
        return

    def b_setAnimState(self, animName, animMultiplier = 1.0, callback = None, extraArgs = []):
        self.d_setAnimState(animName, animMultiplier, None, extraArgs)
        self.setAnimState(animName, animMultiplier, None, None, callback, extraArgs)
        return

    def d_setAnimState(self, animName, animMultiplier = 1.0, timestamp = None, extraArgs = []):
        timestamp = globalClockDelta.getFrameNetworkTime()
        self.sendUpdate('setAnimState', [animName, animMultiplier, timestamp])

    def setAnimState(self, animName, animMultiplier = 1.0, timestamp = None, animType = None, callback = None, extraArgs = []):
        if not animName or animName == 'None':
            return
        if timestamp == None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)
        if base.config.GetBool('check-invalid-anims', True):
            if animMultiplier > 1.0 and animName in ['neutral']:
                animMultiplier = 1.0
        if self.animFSM.getStateNamed(animName):
            self.animFSM.request(animName, [animMultiplier,
             ts,
             callback,
             extraArgs])
        self.cleanupPieInHand()
        return

    def b_setEmoteState(self, animIndex, animMultiplier):
        self.setEmoteState(animIndex, animMultiplier)
        self.d_setEmoteState(animIndex, animMultiplier)

    def d_setEmoteState(self, animIndex, animMultiplier):
        timestamp = globalClockDelta.getFrameNetworkTime()
        self.sendUpdate('setEmoteState', [animIndex, animMultiplier, timestamp])

    def setEmoteState(self, animIndex, animMultiplier, timestamp = None):
        if animIndex == TTEmote.EmoteClear:
            return
        if timestamp == None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)
        callback = None
        extraArgs = []
        extraArgs.insert(0, animIndex)
        self.doEmote(animIndex, animMultiplier, ts, callback, extraArgs)
        return

    def setCogStatus(self, cogStatusList):
        self.cogs = cogStatusList

    def setCogCount(self, cogCountList):
        self.cogCounts = cogCountList
        if hasattr(self, 'suitPage'):
            self.suitPage.updatePage()

    def setCogRadar(self, radar):
        self.cogRadar = radar
        if hasattr(self, 'suitPage'):
            self.suitPage.updateCogRadarButtons(radar)

    def setBuildingRadar(self, radar):
        self.buildingRadar = radar
        if hasattr(self, 'suitPage'):
            self.suitPage.updateBuildingRadarButtons(radar)

    def setCogTypes(self, types):
        self.cogTypes = types
        if self.disguisePage:
            self.disguisePage.updatePage()

    def setCogLevels(self, levels):
        self.cogLevels = levels
        if self.disguisePage:
            self.disguisePage.updatePage()

    def getCogLevels(self):
        return self.cogLevels

    def setCogParts(self, parts):
        self.cogParts = parts
        messenger.send(self.uniqueName('cogMeritsChange'))
        if self.disguisePage:
            self.disguisePage.updatePage()

    def getCogParts(self):
        return self.cogParts

    def setCogMerits(self, merits):
        self.cogMerits = merits
        messenger.send(self.uniqueName('cogMeritsChange'))
        if self.disguisePage:
            self.disguisePage.updatePage()

    def getCogMerits(self):
        return self.cogMerits

    def readyForPromotion(self, dept):
        merits = base.localAvatar.cogMerits[dept]
        totalMerits = CogDisguiseGlobals.getTotalMerits(self, dept)
        if merits >= totalMerits:
            return 1
        else:
            return 0

    def setCogIndex(self, index):
        self.cogIndex = index
        if self.cogIndex == -1:
            if self.isDisguised:
                self.takeOffSuit()
        else:
            parts = self.getCogParts()
            if CogDisguiseGlobals.isSuitComplete(parts, index):
                cogIndex = self.cogTypes[index] + SuitDNA.suitsPerDept * index
                cog = SuitDNA.suitHeadTypes[cogIndex]
                self.putOnSuit(cog)
            else:
                self.putOnSuit(index, rental=True)

    def isCog(self):
        if self.cogIndex == -1:
            return 0
        else:
            return 1

    def setDisguisePageFlag(self, flag):
        if flag and hasattr(self, 'book'):
            self.loadDisguisePages()
        self.disguisePageFlag = flag

    def setSosPageFlag(self, flag):
        if flag and hasattr(self, 'book'):
            self.loadSosPages()
        self.sosPageFlag = flag

    def setFishCollection(self, genusList, speciesList, weightList):
        self.fishCollection = FishCollection.FishCollection()
        self.fishCollection.makeFromNetLists(genusList, speciesList, weightList)

    def getFishCollection(self):
        return self.fishCollection

    def setMaxFishTank(self, maxTank):
        self.maxFishTank = maxTank

    def getMaxFishTank(self):
        return self.maxFishTank

    def setFishTank(self, genusList, speciesList, weightList):
        self.fishTank = FishTank.FishTank()
        self.fishTank.makeFromNetLists(genusList, speciesList, weightList)
        messenger.send(self.uniqueName('fishTankChange'))

    def getFishTank(self):
        return self.fishTank

    def isFishTankFull(self):
        return len(self.fishTank) >= self.maxFishTank

    def setFishingRod(self, rodId):
        self.fishingRod = rodId
        if self == base.localAvatar:
            messenger.send('refreshFishingRod')

    def getFishingRod(self):
        return self.fishingRod
    
    def setMaxFishingRod(self, rodId):
        self.maxFishingRod = rodId
        if self == base.localAvatar:
            messenger.send('refreshFishingRod')

    def getMaxFishingRod(self):
        return self.maxFishingRod
    
    def requestFishingRod(self, rodId):
        if not 0 <= rodId <= self.maxFishingRod:
            return

        self.sendUpdate('requestFishingRod', [rodId])

    def setFishingTrophies(self, trophyList):
        self.fishingTrophies = trophyList

    def getFishingTrophies(self):
        return self.fishingTrophies

    def setQuests(self, flattenedQuests):
        questList = []
        questLen = 5
        for i in xrange(0, len(flattenedQuests), questLen):
            questList.append(flattenedQuests[i:i + questLen])

        self.quests = questList
        if self == base.localAvatar:
            messenger.send('questsChanged')

    def setQuestCarryLimit(self, limit):
        self.questCarryLimit = limit
        if self == base.localAvatar:
            messenger.send('questsChanged')

    def getQuestCarryLimit(self):
        return self.questCarryLimit

    def d_requestDeleteQuest(self, questDesc):
        self.sendUpdate('requestDeleteQuest', [list(questDesc)])

    def setMaxCarry(self, maxCarry):
        self.maxCarry = maxCarry
        if self.inventory:
            self.inventory.updateGUI()

    def getMaxCarry(self):
        return self.maxCarry

    def setCheesyEffect(self, effect, hoodId, expireTime):
        self.savedCheesyEffect = effect
        self.savedCheesyHoodId = hoodId
        self.savedCheesyExpireTime = expireTime
        if self == base.localAvatar:
            self.notify.debug('setCheesyEffect(%s, %s, %s)' % (effect, hoodId, expireTime))
            if effect != ToontownGlobals.CENormal:
                serverTime = time.time() + self.cr.getServerDelta()
                duration = expireTime * 60 - serverTime
                if duration < 0:
                    self.notify.debug('effect should have expired %s ago.' % PythonUtil.formatElapsedSeconds(-duration))
                else:
                    self.notify.debug('effect will expire in %s.' % PythonUtil.formatElapsedSeconds(duration))
        if self.activeState == DistributedObject.ESGenerated:
            self.reconsiderCheesyEffect(lerpTime=0.5)
        else:
            self.reconsiderCheesyEffect()

    def reconsiderCheesyEffect(self, lerpTime = 0):
        effect = self.savedCheesyEffect
        hoodId = self.savedCheesyHoodId
        if not self.cr.areCheesyEffectsAllowed():
            effect = ToontownGlobals.CENormal
        if hoodId != 0:
            try:
                currentHoodId = base.cr.playGame.hood.id
            except:
                currentHoodId = None

            if hoodId == 1:
                if currentHoodId == ToontownGlobals.ToontownCentral:
                    effect = ToontownGlobals.CENormal
            elif currentHoodId != None and currentHoodId != hoodId:
                effect = ToontownGlobals.CENormal
        if self.ghostMode:
            effect = ToontownGlobals.CEGhost
        self.applyCheesyEffect(effect, lerpTime=lerpTime)
        return

    def setGhostMode(self, flag):
        if self.ghostMode != flag:
            self.ghostMode = flag
            if not hasattr(self, 'cr'):
                return
            if self.activeState <= DistributedObject.ESDisabled:
                self.notify.debug('not applying cheesy effect to disabled Toon')
            elif self.activeState == DistributedObject.ESGenerating:
                self.reconsiderCheesyEffect()
            elif self.activeState == DistributedObject.ESGenerated:
                self.reconsiderCheesyEffect(lerpTime=0.5)
            else:
                self.notify.warning('unknown activeState: %s' % self.activeState)
            self.showNametag2d()
            self.showNametag3d()
            if hasattr(self, 'collNode'):
                if self.ghostMode:
                    self.collNode.setCollideMask(ToontownGlobals.GhostBitmask)
                else:
                    self.collNode.setCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.PieBitmask)
            if self.isLocal():
                if self.ghostMode:
                    self.useGhostControls()
                else:
                    self.useWalkControls()

    if hasattr(base, 'wantPets') and base.wantPets:

        def setPetTrickPhrases(self, petTricks):
            self.petTrickPhrases = petTricks
            if self.isLocal():
                messenger.send('petTrickPhrasesChanged')

    def setCustomMessages(self, customMessages):
        self.customMessages = customMessages
        if self.isLocal():
            messenger.send('customMessagesChanged')

    def setResistanceMessages(self, resistanceMessages):
        self.resistanceMessages = resistanceMessages
        if self.isLocal():
            messenger.send('resistanceMessagesChanged')

    def getResistanceMessageCharges(self, textId):
        msgs = self.resistanceMessages
        for i in xrange(len(msgs)):
            if msgs[i][0] == textId:
                return msgs[i][1]

        return 0

    def setCatalogSchedule(self, currentWeek, nextTime):
        self.catalogScheduleCurrentWeek = currentWeek
        self.catalogScheduleNextTime = nextTime
        if self.isLocal():
            self.notify.debug('setCatalogSchedule(%s, %s)' % (currentWeek, nextTime))
            if nextTime:
                serverTime = time.time() + self.cr.getServerDelta()
                duration = nextTime * 60 - serverTime
                self.notify.debug('next catalog in %s.' % PythonUtil.formatElapsedSeconds(duration))

    def setCatalog(self, monthlyCatalog, weeklyCatalog, backCatalog):
        self.monthlyCatalog = CatalogItemList.CatalogItemList(monthlyCatalog)
        self.weeklyCatalog = CatalogItemList.CatalogItemList(weeklyCatalog)
        self.backCatalog = CatalogItemList.CatalogItemList(backCatalog)
        if config.GetBool('want-house-types', False):
            from toontown.catalog import CatalogHouseItem
            self.backCatalog.extend(CatalogHouseItem.getAllHouses())
        if self.catalogNotify == ToontownGlobals.NewItems:
            self.catalogNotify = ToontownGlobals.OldItems

    def setCatalogNotify(self, catalogNotify, mailboxNotify):
        if len(self.weeklyCatalog) + len(self.monthlyCatalog) == 0:
            catalogNotify = ToontownGlobals.NoItems
        if len(self.mailboxContents) == 0:
            mailboxNotify = ToontownGlobals.NoItems
        self.catalogNotify = catalogNotify
        self.mailboxNotify = mailboxNotify
        if self.isLocal():
            self.gotCatalogNotify = 1
            self.refreshOnscreenButtons()

    def setDeliverySchedule(self, onOrder):
        self.onOrder = CatalogItemList.CatalogItemList(onOrder, store=CatalogItem.Customization | CatalogItem.DeliveryDate)
        if self == base.localAvatar:
            nextTime = self.onOrder.getNextDeliveryDate()
            if nextTime != None:
                serverTime = time.time() + self.cr.getServerDelta()
                duration = nextTime * 60 - serverTime
                self.notify.debug('next delivery in %s.' % PythonUtil.formatElapsedSeconds(duration))
            messenger.send('setDeliverySchedule-%s' % self.doId)
        return

    def setMailboxContents(self, mailboxContents):
        self.mailboxContents = CatalogItemList.CatalogItemList(mailboxContents, store=CatalogItem.Customization)
        messenger.send('setMailboxContents-%s' % self.doId)

    def setAwardSchedule(self, onOrder):
        self.onAwardOrder = CatalogItemList.CatalogItemList(onOrder, store=CatalogItem.Customization | CatalogItem.DeliveryDate)
        if self == base.localAvatar:
            nextTime = self.onAwardOrder.getNextDeliveryDate()
            if nextTime != None:
                serverTime = time.time() + self.cr.getServerDelta()
                duration = nextTime * 60 - serverTime
                self.notify.debug('next delivery in %s.' % PythonUtil.formatElapsedSeconds(duration))
            messenger.send('setAwardSchedule-%s' % self.doId)
        return

    def setAwardMailboxContents(self, awardMailboxContents):
        self.notify.debug('Setting awardMailboxContents to %s.' % awardMailboxContents)
        self.awardMailboxContents = CatalogItemList.CatalogItemList(awardMailboxContents, store=CatalogItem.Customization)
        self.notify.debug('awardMailboxContents is %s.' % self.awardMailboxContents)
        messenger.send('setAwardMailboxContents-%s' % self.doId)

    def setAwardNotify(self, awardNotify):
        self.notify.debug('setAwardNotify( %s )' % awardNotify)
        self.awardNotify = awardNotify
        if self.isLocal():
            self.gotCatalogNotify = 1
            self.refreshOnscreenButtons()

    def setGiftSchedule(self, onGiftOrder):
        self.onGiftOrder = CatalogItemList.CatalogItemList(onGiftOrder, store=CatalogItem.Customization | CatalogItem.DeliveryDate)
        if self == base.localAvatar:
            nextTime = self.onGiftOrder.getNextDeliveryDate()
            if nextTime != None:
                serverTime = time.time() + self.cr.getServerDelta()
                duration = nextTime * 60 - serverTime
                self.notify.debug('next delivery in %s.' % PythonUtil.formatElapsedSeconds(duration))
        return

    def playSplashEffect(self, x, y, z):
        if localAvatar.zoneId not in [ToontownGlobals.DonaldsDock, ToontownGlobals.OutdoorZone] and (not hasattr(localAvatar, 'inEstate') or localAvatar.inEstate != 1):
            if random.random() < 0.1:
                self.sendLogSuspiciousEvent('AvatarHackWarning! playing hacked splash effect')
            return
        from toontown.effects import Splash
        if self.splash == None:
            self.splash = Splash.Splash(render)
        self.splash.setPos(x, y, z)
        self.splash.setScale(2)
        self.splash.play()
        place = base.cr.playGame.getPlace()
        if place:
            if hasattr(place.loader, 'submergeSound'):
                base.playSfx(place.loader.submergeSound, node=self)
        return

    def d_playSplashEffect(self, x, y, z):
        self.playSplashEffect(x, y, z)
        self.sendUpdate('playSplashEffect', [x, y, z])

    def setTrackAccess(self, trackArray):
        self.trackArray = trackArray
        if self.inventory:
            self.inventory.updateGUI()

    def getTrackAccess(self):
        return self.trackArray

    def hasTrackAccess(self, track):
        return self.trackArray[track]

    def setTrackProgress(self, trackId, progress):
        self.trackProgressId = trackId
        self.trackProgress = progress
        if hasattr(self, 'trackPage'):
            self.trackPage.updatePage()

    def getTrackProgress(self):
        return [self.trackProgressId, self.trackProgress]

    def getTrackProgressAsArray(self, maxLength = 15):
        shifts = map(operator.rshift, maxLength * [self.trackProgress], range(maxLength - 1, -1, -1))
        digits = map(operator.mod, shifts, maxLength * [2])
        digits.reverse()
        return digits

    def setTeleportAccess(self, teleportZoneArray):
        self.teleportZoneArray = teleportZoneArray

    def getTeleportAccess(self):
        return self.teleportZoneArray

    def hasTeleportAccess(self, zoneId):
        return zoneId in self.teleportZoneArray

    def setScavengerHunt(self, scavengerHuntArray):
        self.scavengerHuntArray = scavengerHuntArray

    def getScavengerHunt(self):
        return self.scavengerHuntArray

    def setQuestHistory(self, questList):
        self.questHistory = questList

    def getQuestHistory(self):
        return self.questHistory

    def setRewardHistory(self, rewardTier, rewardList):
        self.rewardTier = rewardTier
        self.rewardHistory = rewardList

    def getRewardHistory(self):
        return (self.rewardTier, self.rewardHistory)

    def doSmoothTask(self, task):
        self.smoother.computeAndApplySmoothPosHpr(self, self)
        self.setSpeed(self.smoother.getSmoothForwardVelocity(), self.smoother.getSmoothRotationalVelocity())
        return Task.cont

    def d_setParent(self, parentToken):
        DistributedSmoothNode.DistributedSmoothNode.d_setParent(self, parentToken)

    def setEmoteAccess(self, bits):
        self.emoteAccess = bits
        if self == base.localAvatar:
            messenger.send('emotesChanged')

    def b_setHouseId(self, id):
        self.setHouseId(id)
        self.d_setHouseId(id)

    def d_setHouseId(self, id):
        self.sendUpdate('setHouseId', [id])

    def setHouseId(self, id):
        self.houseId = id

    def getHouseId(self):
        return self.houseId

    def b_setSpeedChatStyleIndex(self, index):
        realIndexToSend = 0
        if type(index) == type(0) and 0 <= index and index < len(speedChatStyles):
            realIndexToSend = index
        self.setSpeedChatStyleIndex(realIndexToSend)
        self.d_setSpeedChatStyleIndex(realIndexToSend)

    def d_setSpeedChatStyleIndex(self, index):
        realIndexToSend = 0
        if type(index) == type(0) and 0 <= index and index < len(speedChatStyles):
            realIndexToSend = index
        self.sendUpdate('setSpeedChatStyleIndex', [realIndexToSend])

    def setSpeedChatStyleIndex(self, index):
        realIndexToUse = 0
        if type(index) == type(0) and 0 <= index and index < len(speedChatStyles):
            realIndexToUse = index
        else:
            base.cr.centralLogger.writeClientEvent('Hacker victim setSpeedChatStyleIndex invalid attacking toon = %d' % self.doId)
        self.speedChatStyleIndex = realIndexToUse
        nameKey, arrowColor, rolloverColor, frameColor = speedChatStyles[realIndexToUse]
        self.nametag.setQtColor(VBase4(frameColor[0], frameColor[1], frameColor[2], 1))
        if self.isLocal():
            messenger.send('SpeedChatStyleChange', [])

    def getSpeedChatStyleIndex(self):
        return self.speedChatStyleIndex

    def setMaxMoney(self, maxMoney):
        self.maxMoney = maxMoney

    def getMaxMoney(self):
        return self.maxMoney

    def setMoney(self, money):
        if money != self.money:
            self.money = money
            messenger.send(self.uniqueName('moneyChange'), [self.money])

    def getMoney(self):
        return self.money

    def setMaxBankMoney(self, maxMoney):
        self.maxBankMoney = maxMoney

    def getMaxBankMoney(self):
        return self.maxBankMoney

    def setBankMoney(self, money):
        self.bankMoney = money
        messenger.send(self.uniqueName('bankMoneyChange'), [self.bankMoney])

    def getBankMoney(self):
        return self.bankMoney

    def getTotalMoney(self):
        return self.getBankMoney() + self.getMoney()

    def setEmblems(self, emblems):
        if self.emblems != emblems:
            self.emblems = emblems
            messenger.send(self.uniqueName('emblemsChange'), [self.emblems])

    def getEmblems(self):
        return self.emblems

    def isEnoughEmblemsToBuy(self, itemEmblemPrices):
        for emblemIndex, emblemPrice in enumerate(itemEmblemPrices):
            if emblemIndex >= len(self.emblems):
                return False
            if self.emblems[emblemIndex] < emblemPrice:
                return False

        return True

    def isEnoughMoneyAndEmblemsToBuy(self, moneyPrice, itemEmblemPrices):
        if self.getTotalMoney() < moneyPrice:
            return False
        for emblemIndex, emblemPrice in enumerate(itemEmblemPrices):
            if emblemIndex >= len(self.emblems):
                return False
            if self.emblems[emblemIndex] < emblemPrice:
                return False

        return True

    def presentPie(self, x, y, z, h, timestamp32):
        if self.numPies <= 0:
            return
        lastTossTrack = Sequence()
        if self.tossTrack:
            lastTossTrack = self.tossTrack
            tossTrack = None
        ts = globalClockDelta.localElapsedTime(timestamp32, bits=32)
        ts -= self.smoother.getDelay()
        ival = self.getPresentPieInterval(x, y, z, h)
        if ts > 0:
            startTime = ts
            lastTossTrack.finish()
        else:
            ival = Sequence(Wait(-ts), ival)
            lastTossTrack.finish()
            startTime = 0
        ival = Sequence(ival)
        ival.start(startTime)
        self.tossTrack = ival
        return

    def tossPie(self, x, y, z, h, sequence, power, throwType, timestamp32):
        if self.numPies <= 0:
            return
        if self.numPies != ToontownGlobals.FullPies:
            self.setNumPies(self.numPies - 1)
        self.lastTossedPie = globalClock.getFrameTime()
        lastTossTrack = Sequence()
        if self.tossTrack:
            lastTossTrack = self.tossTrack
            tossTrack = None
        lastPieTrack = Sequence()
        if sequence in self.pieTracks:
            lastPieTrack = self.pieTracks[sequence]
            del self.pieTracks[sequence]
        ts = globalClockDelta.localElapsedTime(timestamp32, bits=32)
        ts -= self.smoother.getDelay()
        toss, pie, flyPie = self.getTossPieInterval(x, y, z, h, power, throwType)
        if ts > 0:
            startTime = ts
            lastTossTrack.finish()
            lastPieTrack.finish()
        else:
            toss = Sequence(Wait(-ts), toss)
            pie = Sequence(Wait(-ts), pie)
            lastTossTrack.finish()
            lastPieTrack.finish()
            startTime = 0
        self.tossTrack = toss
        toss.start(startTime)
        pie = Sequence(pie, Func(self.pieFinishedFlying, sequence))
        self.pieTracks[sequence] = pie
        pie.start(startTime)

    def pieFinishedFlying(self, sequence):
        if sequence in self.pieTracks:
            del self.pieTracks[sequence]

    def pieFinishedSplatting(self, sequence):
        if sequence in self.splatTracks:
            del self.splatTracks[sequence]

    def pieSplat(self, x, y, z, sequence, pieCode, timestamp32):
        if self.isLocal():
            return
        elapsed = globalClock.getFrameTime() - self.lastTossedPie
        if elapsed > 30:
            return
        lastPieTrack = Sequence()
        if sequence in self.pieTracks:
            lastPieTrack = self.pieTracks[sequence]
            del self.pieTracks[sequence]
        if sequence in self.splatTracks:
            lastSplatTrack = self.splatTracks[sequence]
            del self.splatTracks[sequence]
            lastSplatTrack.finish()
        ts = globalClockDelta.localElapsedTime(timestamp32, bits=32)
        ts -= self.smoother.getDelay()
        splat = self.getPieSplatInterval(x, y, z, pieCode)
        splat = Sequence(Func(messenger.send, 'pieSplat', [self, pieCode]), splat)
        if ts > 0:
            startTime = ts
            lastPieTrack.finish()
        else:
            splat = Sequence(Wait(-ts), splat)
            startTime = 0
        splat = Sequence(splat, Func(self.pieFinishedSplatting, sequence))
        self.splatTracks[sequence] = splat
        splat.start(startTime)

    def cleanupPies(self):
        for track in self.pieTracks.values():
            track.finish()

        self.pieTracks = {}
        for track in self.splatTracks.values():
            track.finish()

        self.splatTracks = {}
        self.cleanupPieInHand()

    def cleanupPieInHand(self):
        if self.tossTrack:
            self.tossTrack.finish()
            self.tossTrack = None
        self.cleanupPieModel()

    def setNumPies(self, numPies):
        self.numPies = numPies
        if self.isLocal():
            self.updatePieButton()
            if numPies == 0:
                self.interruptPie()

    def setPieType(self, pieType):
        self.pieType = pieType
        if self.isLocal():
            self.updatePieButton()

    def setTrophyScore(self, score):
        self.trophyScore = score
        if self.trophyStar != None:
            self.trophyStar.removeNode()
            self.trophyStar = None
        if self.trophyStarSpeed != 0:
            taskMgr.remove(self.uniqueName('starSpin'))
            self.trophyStarSpeed = 0
        if self.trophyScore >= ToontownGlobals.TrophyStarLevels[4]:
            self.trophyStar = loader.loadModel('phase_3.5/models/gui/name_star')
            np = NodePath(self.nametag.getNameIcon())
            self.trophyStar.reparentTo(np)
            self.trophyStar.setScale(2)
            self.trophyStar.setColor(ToontownGlobals.TrophyStarColors[4])
            self.trophyStarSpeed = 15
            if self.trophyScore >= ToontownGlobals.TrophyStarLevels[5]:
                taskMgr.add(self.__starSpin, self.uniqueName('starSpin'))
        elif self.trophyScore >= ToontownGlobals.TrophyStarLevels[2]:
            self.trophyStar = loader.loadModel('phase_3.5/models/gui/name_star')
            np = NodePath(self.nametag.getNameIcon())
            self.trophyStar.reparentTo(np)
            self.trophyStar.setScale(1.5)
            self.trophyStar.setColor(ToontownGlobals.TrophyStarColors[2])
            self.trophyStarSpeed = 10
            if self.trophyScore >= ToontownGlobals.TrophyStarLevels[3]:
                taskMgr.add(self.__starSpin, self.uniqueName('starSpin'))
        elif self.trophyScore >= ToontownGlobals.TrophyStarLevels[0]:
            self.trophyStar = loader.loadModel('phase_3.5/models/gui/name_star')
            np = NodePath(self.nametag.getNameIcon())
            self.trophyStar.reparentTo(np)
            self.trophyStar.setScale(1.5)
            self.trophyStar.setColor(ToontownGlobals.TrophyStarColors[0])
            self.trophyStarSpeed = 8
            if self.trophyScore >= ToontownGlobals.TrophyStarLevels[1]:
                taskMgr.add(self.__starSpin, self.uniqueName('starSpin'))
        self.setHeadPositions()

    def __starSpin(self, task):
        now = globalClock.getFrameTime()
        r = now * self.trophyStarSpeed % 360.0
        self.trophyStar.setR(r)
        return Task.cont

    def getZoneId(self):
        place = base.cr.playGame.getPlace()
        if place:
            return place.getZoneId()
        else:
            return None
        return None

    def getRequestID(self):
        return CLIENT_GET_AVATAR_DETAILS

    def announceBingo(self):
        self.setChatAbsolute(TTLocalizer.FishBingoBingo, CFSpeech | CFTimeout)

    def squish(self, damage):
        if self == base.localAvatar:
            base.cr.playGame.getPlace().fsm.request('squished')
            self.stunToon()
            self.setZ(self.getZ(render) + 0.025)

    def d_squish(self, damage):
        self.sendUpdate('squish', [damage])

    def b_squish(self, damage):
        if not self.isStunned:
            self.squish(damage)
            self.d_squish(damage)
            self.playDialogueForString('!')

    def getShadowJoint(self):
        return Toon.Toon.getShadowJoint(self)

    if base.wantKarts:

        def hasKart(self):
            return self.kartDNA[KartDNA.bodyType] != -1

        def getKartDNA(self):
            return self.kartDNA

        def setTickets(self, numTickets):
            self.tickets = numTickets

        def getTickets(self):
            return self.tickets

        def getAccessoryByType(self, accType):
            return self.kartDNA[accType]

        def setCurrentKart(self, avId):
            self.kartId = avId

        def releaseKart(self):
            self.kartId = None
            return

        def setKartBodyType(self, bodyType):
            self.kartDNA[KartDNA.bodyType] = bodyType

        def getKartBodyType(self):
            return self.kartDNA[KartDNA.bodyType]

        def setKartBodyColor(self, bodyColor):
            self.kartDNA[KartDNA.bodyColor] = bodyColor

        def getKartBodyColor(self):
            return self.kartDNA[KartDNA.bodyColor]

        def setKartAccessoryColor(self, accColor):
            self.kartDNA[KartDNA.accColor] = accColor

        def getKartAccessoryColor(self):
            return self.kartDNA[KartDNA.accColor]

        def setKartEngineBlockType(self, ebType):
            self.kartDNA[KartDNA.ebType] = ebType

        def getKartEngineBlockType(self):
            return self.kartDNA[KartDNA.ebType]

        def setKartSpoilerType(self, spType):
            self.kartDNA[KartDNA.spType] = spType

        def getKartSpoilerType(self):
            return self.kartDNA[KartDNA.spType]

        def setKartFrontWheelWellType(self, fwwType):
            self.kartDNA[KartDNA.fwwType] = fwwType

        def getKartFrontWheelWellType(self):
            return self.kartDNA[KartDNA.fwwType]

        def setKartBackWheelWellType(self, bwwType):
            self.kartDNA[KartDNA.bwwType] = bwwType

        def getKartBackWheelWellType(self):
            return self.kartDNA[KartDNA.bwwType]

        def setKartRimType(self, rimsType):
            self.kartDNA[KartDNA.rimsType] = rimsType

        def setKartDecalType(self, decalType):
            self.kartDNA[KartDNA.decalType] = decalType

        def getKartDecalType(self):
            return self.kartDNA[KartDNA.decalType]

        def getKartRimType(self):
            return self.kartDNA[KartDNA.rimsType]

        def setKartAccessoriesOwned(self, accessories):
            while len(accessories) < 16:
                accessories.append(-1)

            self.accessories = accessories

        def getKartAccessoriesOwned(self):
            owned = copy.deepcopy(self.accessories)
            while InvalidEntry in owned:
                owned.remove(InvalidEntry)

            return owned

        def requestKartDNAFieldUpdate(self, dnaField, fieldValue):
            self.notify.debug('requestKartDNAFieldUpdate - dnaField %s, fieldValue %s' % (dnaField, fieldValue))
            self.sendUpdate('updateKartDNAField', [dnaField, fieldValue])

        def requestAddOwnedAccessory(self, accessoryId):
            self.notify.debug('requestAddOwnedAccessor - purchased accessory %s' % accessoryId)
            self.sendUpdate('addOwnedAccessory', [accessoryId])

        def requestRemoveOwnedAccessory(self, accessoryId):
            self.notify.debug('requestRemoveOwnedAccessor - removed accessory %s' % accessoryId)
            self.sendUpdate('removeOwnedAccessory', [accessoryId])

        def setKartingTrophies(self, trophyList):
            self.kartingTrophies = trophyList

        def getKartingTrophies(self):
            return self.kartingTrophies

        def setKartingHistory(self, history):
            self.kartingHistory = history

        def getKartingHistory(self):
            return self.kartingHistory

        def setKartingPersonalBest(self, bestTimes):
            self.kartingPersonalBest = bestTimes

        def getKartingPersonalBest(self):
            return self.kartingPersonalBest

        def setKartingPersonalBest2(self, bestTimes2):
            self.kartingPersonalBest2 = bestTimes2

        def getKartingPersonalBest2(self):
            return self.kartingPersonalBest2

        def getKartingPersonalBestAll(self):
            return self.kartingPersonalBest + self.kartingPersonalBest2

    if hasattr(base, 'wantPets') and base.wantPets:

        def setPetId(self, petId):
            self.petId = petId
            if self.isLocal():
                base.cr.addPetToFriendsMap()

        def getPetId(self):
            return self.petId

        def hasPet(self):
            return self.petId != 0

        def b_setPetTutorialDone(self, done):
            self.d_setPetTutorialDone(done)
            self.setPetTutorialDone(done)

        def d_setPetTutorialDone(self, done):
            self.sendUpdate('setPetTutorialDone', [done])

        def setPetTutorialDone(self, done):
            self.petTutorialDone = done

        def b_setFishBingoTutorialDone(self, done):
            self.d_setFishBingoTutorialDone(done)
            self.setFishBingoTutorialDone(done)

        def d_setFishBingoTutorialDone(self, done):
            self.sendUpdate('setFishBingoTutorialDone', [done])

        def setFishBingoTutorialDone(self, done):
            self.fishBingoTutorialDone = done

        def b_setFishBingoMarkTutorialDone(self, done):
            self.d_setFishBingoMarkTutorialDone(done)
            self.setFishBingoMarkTutorialDone(done)

        def d_setFishBingoMarkTutorialDone(self, done):
            self.sendUpdate('setFishBingoMarkTutorialDone', [done])

        def setFishBingoMarkTutorialDone(self, done):
            self.fishBingoMarkTutorialDone = done

        def b_setPetMovie(self, petId, flag):
            self.d_setPetMovie(petId, flag)
            self.setPetMovie(petId, flag)

        def d_setPetMovie(self, petId, flag):
            self.sendUpdate('setPetMovie', [petId, flag])

        def setPetMovie(self, petId, flag):
            pass

        def lookupPetDNA(self):
            if self.petId and not self.petDNA:
                from toontown.pets import PetDetail
                PetDetail.PetDetail(self.petId, self.__petDetailsLoaded)

        def __petDetailsLoaded(self, pet):
            self.petDNA = pet.style


    def trickOrTreatTargetMet(self, beanAmount):
        if self.effect:
            self.effect.stop()
        self.effect = TrickOrTreatTargetEffect(beanAmount)
        self.effect.play()

    def winterCarolingTargetMet(self, beanAmount):
        if self.effect:
            self.effect.stop()
        self.effect = WinterCarolingEffect(beanAmount)
        self.effect.play()

    def d_reqCogSummons(self, type, suitIndex):
        self.sendUpdate('reqCogSummons', [type, suitIndex])

    def cogSummonsResponse(self, returnCode, suitIndex, doId):
        messenger.send('cog-summons-response', [returnCode, suitIndex, doId])

    def setCogSummonsEarned(self, cogSummonsEarned):
        self.cogSummonsEarned = cogSummonsEarned

    def getCogSummonsEarned(self):
        return self.cogSummonsEarned

    def hasCogSummons(self, suitIndex, type = None):
        summons = self.getCogSummonsEarned()
        curSetting = summons[suitIndex]
        if type == 'building':
            return curSetting & 1
        elif type == 'invasion':
            return curSetting & 2
        elif type == 'cogdo':
            return curSetting & 4
        elif type == 'skelinvasion':
            return curSetting & 8
        elif type == 'waiterinvasion':
            return curSetting & 16
        elif type == 'v2invasion':
            return curSetting & 32
        return curSetting

    def setFlowerCollection(self, speciesList, varietyList):
        self.flowerCollection = FlowerCollection.FlowerCollection()
        self.flowerCollection.makeFromNetLists(speciesList, varietyList)

    def getFlowerCollection(self):
        return self.flowerCollection

    def setMaxFlowerBasket(self, maxFlowerBasket):
        self.maxFlowerBasket = maxFlowerBasket

    def getMaxFlowerBasket(self):
        return self.maxFlowerBasket

    def isFlowerBasketFull(self):
        return len(self.flowerBasket) >= self.maxFlowerBasket

    def setFlowerBasket(self, speciesList, varietyList):
        self.flowerBasket = FlowerBasket.FlowerBasket()
        self.flowerBasket.makeFromNetLists(speciesList, varietyList)
        messenger.send('flowerBasketUpdated')

    def getFlowerBasket(self):
        return self.flowerBasket

    def setShovel(self, shovelId):
        self.shovel = shovelId

    def attachShovel(self):
        self.shovelModel = self.getShovelModel()
        self.shovelModel.reparentTo(self.rightHand)
        return self.shovelModel

    def detachShovel(self):
        if self.shovelModel:
            self.shovelModel.removeNode()

    def getShovelModel(self):
        shovels = loader.loadModel('phase_5.5/models/estate/shovels')
        shovelId = ['A',
         'B',
         'C',
         'D'][self.shovel]
        shovel = shovels.find('**/shovel' + shovelId)
        shovel.setH(-90)
        shovel.setP(216)
        shovel.setX(0.2)
        shovel.detachNode()
        shovels.removeNode()
        return shovel

    def setShovelSkill(self, skillLevel):
        self.shovelSkill = skillLevel

    def getBoxCapability(self):
        return GardenGlobals.getShovelPower(self.shovel, self.shovelSkill)

    def setWateringCan(self, wateringCanId):
        self.wateringCan = wateringCanId

    def attachWateringCan(self):
        self.wateringCanModel = self.getWateringCanModel()
        self.wateringCanModel.reparentTo(self.rightHand)
        return self.wateringCanModel

    def detachWateringCan(self):
        if self.wateringCanModel:
            self.wateringCanModel.removeNode()

    def getWateringCanModel(self):
        scalePosHprsTable = ((0.25, 0.1, 0, 0.2, -90, -125, -45),
         (0.2, 0.0, 0.25, 0.2, -90, -125, -45),
         (0.2, 0.2, 0.1, 0.2, -90, -125, -45),
         (0.2, 0.0, 0.25, 0.2, -90, -125, -45))
        cans = loader.loadModel('phase_5.5/models/estate/watering_cans')
        canId = ['A',
         'B',
         'C',
         'D'][self.wateringCan]
        can = cans.find('**/water_can' + canId)
        can.setScale(scalePosHprsTable[self.wateringCan][0])
        can.setPos(scalePosHprsTable[self.wateringCan][1], scalePosHprsTable[self.wateringCan][2], scalePosHprsTable[self.wateringCan][3])
        can.setHpr(scalePosHprsTable[self.wateringCan][4], scalePosHprsTable[self.wateringCan][5], scalePosHprsTable[self.wateringCan][6])
        can.detachNode()
        cans.removeNode()
        if hasattr(base, 'rwc'):
            if base.rwc:
                if hasattr(self, 'wateringCan2'):
                    self.wateringCan2.removeNode()
                self.wateringCan2 = can.copyTo(self.rightHand)
            else:
                self.wateringCan2.removeNode()
        return can

    def setWateringCanSkill(self, skillLevel):
        self.wateringCanSkill = skillLevel

    def setGardenSpecials(self, specials):
        self.gardenSpecials = specials
        if hasattr(self, 'gardenPage') and self.gardenPage:
            self.gardenPage.updatePage()

    def getGardenSpecials(self):
        return self.gardenSpecials

    def getMyTrees(self):
        treeDict = self.cr.getObjectsOfClass(DistributedGagTree.DistributedGagTree)
        trees = []
        for tree in treeDict.values():
            if tree.getOwnerId() == self.doId:
                trees.append(tree)

        if not trees:
            pass
        return trees

    def isTreePlanted(self, track, level):
        trees = self.getMyTrees()
        for tree in trees:
            if tree.gagTrack == track and tree.gagLevel == level:
                return True

        return False

    def doIHaveRequiredTrees(self, track, level):
        trees = self.getMyTrees()
        trackAndLevelList = []
        for tree in trees:
            trackAndLevelList.append((tree.gagTrack, tree.gagLevel))

        haveRequired = True
        for curLevel in xrange(level):
            testTuple = (track, curLevel)
            if testTuple not in trackAndLevelList:
                haveRequired = False
                break

        return haveRequired

    def setTrackBonusLevel(self, trackArray):
        self.trackBonusLevel = trackArray
        if self.inventory:
            self.inventory.updateGUI()

    def getTrackBonusLevel(self, track = None):
        if track == None:
            return self.trackBonusLevel
        else:
            return self.trackBonusLevel[track]
        return

    def checkGagBonus(self, track, level):
        trackBonus = self.getTrackBonusLevel(track)
        return trackBonus >= level

    def setGardenTrophies(self, trophyList):
        self.gardenTrophies = trophyList

    def getGardenTrophies(self):
        return self.gardenTrophies

    def useSpecialResponse(self, returnCode):
        messenger.send('use-special-response', [returnCode])

    def setGardenStarted(self, bStarted):
        self.gardenStarted = bStarted

    def getGardenStarted(self):
        return self.gardenStarted

    def sendToGolfCourse(self, zoneId):
        hoodId = self.cr.playGame.hood.hoodId
        golfRequest = {'loader': 'safeZoneLoader',
         'where': 'golfcourse',
         'how': 'teleportIn',
         'hoodId': hoodId,
         'zoneId': zoneId,
         'shardId': None,
         'avId': -1}
        base.cr.playGame.getPlace().requestLeave(golfRequest)
        return

    def getGolfTrophies(self):
        return self.golfTrophies

    def getGolfCups(self):
        return self.golfCups

    def setGolfHistory(self, history):
        self.golfHistory = history
        self.golfTrophies = GolfGlobals.calcTrophyListFromHistory(self.golfHistory)
        self.golfCups = GolfGlobals.calcCupListFromHistory(self.golfHistory)
        if hasattr(self, 'book'):
            self.addGolfPage()

    def getGolfHistory(self):
        return self.golfHistory

    def hasPlayedGolf(self):
        retval = False
        for historyValue in self.golfHistory:
            if historyValue:
                retval = True
                break

        return retval

    def setPackedGolfHoleBest(self, packedHoleBest):
        unpacked = GolfGlobals.unpackGolfHoleBest(packedHoleBest)
        self.setGolfHoleBest(unpacked)

    def setGolfHoleBest(self, holeBest):
        self.golfHoleBest = holeBest

    def getGolfHoleBest(self):
        return self.golfHoleBest

    def setGolfCourseBest(self, courseBest):
        self.golfCourseBest = courseBest

    def getGolfCourseBest(self):
        return self.golfCourseBest

    def setUnlimitedSwing(self, unlimitedSwing):
        self.unlimitedSwing = unlimitedSwing

    def getUnlimitedSwing(self):
        return self.unlimitedSwing

    def getPinkSlips(self):
        return self.specialInventory[0]
    
    def getCrateKeys(self):
        return self.specialInventory[1]

    def setSpecialInventory(self, specialInventory):
        self.specialInventory = specialInventory
    
    def getSpecialInventory(self):
        return self.specialInventory

    def setDisplayName(self, str):
        if not self.isDisguised:
            self.setFancyNametag(name=str)
        else:
            self.removeFancyNametag()
            Avatar.Avatar.setDisplayName(self, str)

    def setFancyNametag(self, name=None):
        if name is None:
            name = self.getName()
        if self.getNametagStyle() >= len(TTLocalizer.NametagFonts):
            self.nametag.setFont(ToontownGlobals.getToonFont())
        else:
            self.nametag.setFont(ToontownGlobals.getNametagFont(self.getNametagStyle()))
        Avatar.Avatar.setDisplayName(self, name)

    def removeFancyNametag(self):
        self.nametag.clearShadow()

    def getNametagStyle(self):
        if hasattr(self, 'nametagStyle'):
            return self.nametagStyle
        else:
            return 0

    def setNametagStyle(self, nametagStyle):
        if hasattr(self, 'gmToonLockStyle') and self.gmToonLockStyle:
            return
        if base.config.GetBool('want-nametag-avids', 0):
            nametagStyle = 0
        self.nametagStyle = nametagStyle
        self.setDisplayName(self.getName())
    
    def getNametagStyles(self):
        return self.nametagStyles
    
    def setNametagStyles(self, nametagStyles):
        self.nametagStyles = nametagStyles
        if self == base.localAvatar:
            messenger.send('refreshNametagStyle')
    
    def requestNametagStyle(self, nametagStyle):
        if nametagStyle not in self.nametagStyles:
            return
        
        self.sendUpdate('requestNametagStyle', [nametagStyle])

    def getAvIdName(self):
        return '%s\n%s' % (self.getName(), self.doId)

    def playCurrentDialogue(self, dialogue, chatFlags, interrupt = 1):
        if interrupt and self.__currentDialogue is not None:
            self.__currentDialogue.stop()
        self.__currentDialogue = dialogue
        if dialogue:
            base.playSfx(dialogue, node=self)
        elif chatFlags & CFSpeech != 0:
            if self.nametag.getNumChatPages() > 0:
                self.playDialogueForString(self.nametag.getChat())
                if self.soundChatBubble != None:
                    base.playSfx(self.soundChatBubble, node=self)
            elif self.nametag.getChatStomp():
                self.playDialogueForString(self.nametag.getStompText(), self.nametag.getStompDelay())

    def playDialogueForString(self, chatString, delay = 0.0):
        if len(chatString) == 0:
            return
        searchString = chatString.lower()
        if searchString.find(OTPLocalizer.DialogSpecial) >= 0:
            type = 'special'
        elif searchString.find(OTPLocalizer.DialogExclamation) >= 0:
            type = 'exclamation'
        elif searchString.find(OTPLocalizer.DialogQuestion) >= 0:
            type = 'question'
        elif random.randint(0, 1):
            type = 'statementA'
        else:
            type = 'statementB'
        stringLength = len(chatString)
        if stringLength <= OTPLocalizer.DialogLength1:
            length = 1
        elif stringLength <= OTPLocalizer.DialogLength2:
            length = 2
        elif stringLength <= OTPLocalizer.DialogLength3:
            length = 3
        else:
            length = 4
        self.playDialogue(type, length, delay)

    def playDialogue(self, type, length, delay = 0.0):
        dialogueArray = self.getDialogueArray()
        if dialogueArray == None:
            return
        sfxIndex = None
        if type == 'statementA' or type == 'statementB':
            if length == 1:
                sfxIndex = 0
            elif length == 2:
                sfxIndex = 1
            elif length >= 3:
                sfxIndex = 2
        elif type == 'question':
            sfxIndex = 3
        elif type == 'exclamation':
            sfxIndex = 4
        elif type == 'special':
            sfxIndex = 5
        else:
            self.notify.error('unrecognized dialogue type: ', type)
        if sfxIndex != None and sfxIndex < len(dialogueArray) and dialogueArray[sfxIndex] != None:
            soundSequence = Sequence(Wait(delay), SoundInterval(dialogueArray[sfxIndex], node=None, listenerNode=base.localAvatar, loop=0, volume=1.0))
            self.soundSequenceList.append(soundSequence)
            soundSequence.start()
            self.cleanUpSoundList()
        return

    def cleanUpSoundList(self):
        removeList = []
        for soundSequence in self.soundSequenceList:
            if soundSequence.isStopped():
                removeList.append(soundSequence)

        for soundSequence in removeList:
            self.soundSequenceList.remove(soundSequence)

    def setChatAbsolute(self, chatString, chatFlags, dialogue = None, interrupt = 1, quiet = 0):
        DistributedAvatar.DistributedAvatar.setChatAbsolute(self, chatString, chatFlags, dialogue, interrupt)

    def displayTalk(self, chatString):
        flags = CFSpeech | CFTimeout
        if ChatUtil.isThought(chatString):
            flags = CFThought
            chatString = ChatUtil.removeThoughtPrefix(chatString)
        self.nametag.setChat(chatString, flags)
        if base.toonChatSounds:
            self.playCurrentDialogue(None, flags, interrupt=1)

    def setMail(self, mail):
        DistributedToon.partyNotify.debug('setMail called with %d mail items' % len(mail))
        self.mail = []
        for i in xrange(len(mail)):
            oneMailItem = mail[i]
            newMail = SimpleMailBase(*oneMailItem)
            self.mail.append(newMail)

    def setSimpleMailNotify(self, simpleMailNotify):
        DistributedToon.partyNotify.debug('setSimpleMailNotify( %s )' % simpleMailNotify)
        self.simpleMailNotify = simpleMailNotify
        if self.isLocal():
            self.gotCatalogNotify = 1
            self.refreshOnscreenButtons()

    def setInviteMailNotify(self, inviteMailNotify):
        DistributedToon.partyNotify.debug('setInviteMailNotify( %s )' % inviteMailNotify)
        self.inviteMailNotify = inviteMailNotify
        if self.isLocal():
            self.gotCatalogNotify = 1
            self.refreshOnscreenButtons()

    def setInvites(self, invites):
        DistributedToon.partyNotify.debug('setInvites called passing in %d invites.' % len(invites))
        self.invites = []
        for i in xrange(len(invites)):
            oneInvite = invites[i]
            newInvite = InviteInfo(*oneInvite)
            self.invites.append(newInvite)

    def updateInviteMailNotify(self):
        invitesInMailbox = self.getInvitesToShowInMailbox()
        newInvites = 0
        readButNotRepliedInvites = 0
        for invite in invitesInMailbox:
            if invite.status == PartyGlobals.InviteStatus.NotRead:
                newInvites += 1
            elif invite.status == PartyGlobals.InviteStatus.ReadButNotReplied:
                readButNotRepliedInvites += 1

        if newInvites:
            self.setInviteMailNotify(ToontownGlobals.NewItems)
        elif readButNotRepliedInvites:
            self.setInviteMailNotify(ToontownGlobals.OldItems)
        else:
            self.setInviteMailNotify(ToontownGlobals.NoItems)

    def getInvitesToShowInMailbox(self):
        result = []
        for invite in self.invites:
            appendInvite = True
            if invite.status == InviteStatus.Accepted or invite.status == InviteStatus.Rejected:
                appendInvite = False
            if appendInvite:
                partyInfo = self.getOnePartyInvitedTo(invite.partyId)
                if not partyInfo:
                    appendInvite = False
                if appendInvite:
                    if partyInfo.status == PartyGlobals.PartyStatus.Cancelled:
                        appendInvite = False
                if appendInvite:
                    endDate = partyInfo.endTime.date()
                    curDate = base.cr.toontownTimeManager.getCurServerDateTime().date()
                    if endDate < curDate:
                        appendInvite = False
            if appendInvite:
                result.append(invite)

        return result

    def getNumInvitesToShowInMailbox(self):
        result = len(self.getInvitesToShowInMailbox())
        return result

    def setHostedParties(self, hostedParties):
        DistributedToon.partyNotify.debug('setHostedParties called passing in %d parties.' % len(hostedParties))
        self.hostedParties = []
        for i in xrange(len(hostedParties)):
            hostedInfo = hostedParties[i]
            newParty = PartyInfo(*hostedInfo)
            self.hostedParties.append(newParty)

    def setPartiesInvitedTo(self, partiesInvitedTo):
        DistributedToon.partyNotify.debug('setPartiesInvitedTo called passing in %d parties.' % len(partiesInvitedTo))
        self.partiesInvitedTo = []
        for i in xrange(len(partiesInvitedTo)):
            partyInfo = partiesInvitedTo[i]
            newParty = PartyInfo(*partyInfo)
            self.partiesInvitedTo.append(newParty)

        self.updateInviteMailNotify()

    def getOnePartyInvitedTo(self, partyId):
        result = None
        for i in xrange(len(self.partiesInvitedTo)):
            partyInfo = self.partiesInvitedTo[i]
            if partyInfo.partyId == partyId:
                result = partyInfo
                break

        return result

    def getInviteForPartyId(self, partyId):
        result = None
        for invite in self.invites:
            if invite.partyId == partyId:
                result = invite
                break

        return result

    def setPartyReplies(self, replies):
        DistributedToon.partyNotify.debug('setPartyReplies called passing in %d parties.' % len(replies))
        self.partyReplyInfoBases = []
        for i in xrange(len(replies)):
            partyReply = replies[i]
            repliesForOneParty = PartyReplyInfoBase(*partyReply)
            self.partyReplyInfoBases.append(repliesForOneParty)

    def setPartyCanStart(self, partyId):
        DistributedToon.partyNotify.debug('setPartyCanStart called passing in partyId=%s' % partyId)
        for partyInfo in self.hostedParties:
            if partyInfo.partyId == partyId:
                partyInfo.status = PartyGlobals.PartyStatus.CanStart
                from toontown.shtiker import EventsPage
                if hasattr(self, 'eventsPage') and base.localAvatar.book.entered and base.localAvatar.book.isOnPage(self.eventsPage) and self.eventsPage.getMode() == EventsPage.EventsPage_Host:
                    base.localAvatar.eventsPage.loadHostedPartyInfo()
                if hasattr(self, 'displaySystemClickableWhisper'):
                    self.displaySystemClickableWhisper(0, TTLocalizer.PartyCanStart, whisperType=WTSystem)
                else:
                    self.setSystemMessage(0, TTLocalizer.PartyCanStart)

    def setPartyStatus(self, partyId, newStatus):
        DistributedToon.partyNotify.debug('setPartyCanStatus called passing in partyId=%s status=%s' % (partyId, newStatus))
        found = False
        for partyInfo in self.hostedParties:
            if partyInfo.partyId == partyId:
                partyInfo.status = newStatus
                found = True
                break

        for partyInfo in self.partiesInvitedTo:
            if partyInfo.partyId == partyId:
                partyInfo.status = newStatus
                found = True
                from toontown.shtiker import EventsPage
                if hasattr(self, 'eventsPage') and base.localAvatar.book.entered and base.localAvatar.book.isOnPage(self.eventsPage) and self.eventsPage.getMode() == EventsPage.EventsPage_Invited:
                    base.localAvatar.eventsPage.loadInvitations()
                if newStatus == PartyStatus.Started and hasattr(self, 'displaySystemClickableWhisper'):
                    invite = self.getInviteForPartyId(partyId)
                    if invite:
                        name = ' '
                        host = base.cr.identifyAvatar(partyInfo.hostId)
                        if host:
                            name = host.getName()
                        if invite.status == InviteStatus.Accepted:
                            displayStr = TTLocalizer.PartyHasStartedAcceptedInvite % TTLocalizer.GetPossesive(name)
                            self.displaySystemClickableWhisper(-1, displayStr, whisperType=WTSystem)
                        else:
                            displayStr = TTLocalizer.PartyHasStartedNotAcceptedInvite % TTLocalizer.GetPossesive(name)
                            self.setSystemMessage(partyInfo.hostId, displayStr, whisperType=WTSystem)
                break

        if not found:
            self.notify.warning("setPartyCanStart can't find partyId=% status=%d" % (partyId, newStatus))

    def announcePartyStarted(self, partyId):
        DistributedToon.partyNotify.debug('announcePartyStarted')
        return
        for partyReplyInfo in self.partyReplyInfoBases:
            if partyReplyInfo.partyId == partyId:
                for singleReply in partyReplyInfo.replies:
                    toonId = singleReply.inviteeId
                    if base.cr.isFriend(toonId):
                        if base.cr.isFriendOnline(toonId):
                            if singleReply.status == InviteStatus.Accepted:
                                self.whisperSCTo(5302, toonId)
                            else:
                                self.whisperSCTo(5302, toonId)

    def updateInvite(self, inviteKey, newStatus):
        DistributedToon.partyNotify.debug('updateInvite( inviteKey=%d, newStatus=%s )' % (inviteKey, InviteStatus.getString(newStatus)))
        for invite in self.invites:
            if invite.inviteKey == inviteKey:
                invite.status = newStatus
                self.updateInviteMailNotify()
                break

    def updateReply(self, partyId, inviteeId, newStatus):
        DistributedToon.partyNotify.debug('updateReply( partyId=%d, inviteeId=%d, newStatus=%s )' % (partyId, inviteeId, InviteStatus.getString(newStatus)))
        for partyReplyInfoBase in self.partyReplyInfoBases:
            if partyReplyInfoBase.partyId == partyId:
                for reply in partyReplyInfoBase.replies:
                    if reply.inviteeId == inviteeId:
                        reply.status = newStatus
                        break

    def toonUp(self, hpGained, hasInteractivePropBonus = False):
        if self.hp == None or hpGained < 0:
            return
        oldHp = self.hp
        if self.hp + hpGained <= 0:
            self.hp += hpGained
        else:
            self.hp = min(max(self.hp, 0) + hpGained, self.maxHp)
        hpGained = self.hp - max(oldHp, 0)
        if hpGained > 0:
            self.showHpText(hpGained, hasInteractivePropBonus=hasInteractivePropBonus)
            self.hpChange(quietly=0)
        return

    def showHpText(self, number, bonus = 0, scale = 1, hasInteractivePropBonus = False):
        if self.HpTextEnabled and not self.ghostMode:
            if number != 0:
                if self.hpText:
                    self.hideHpText()
                self.HpTextGenerator.setFont(OTPGlobals.getSignFont())
                if number < 0:
                    self.HpTextGenerator.setText(str(number))
                else:
                    hpGainedStr = '+' + str(number)
                    if hasInteractivePropBonus:
                        hpGainedStr += '\n' + TTLocalizer.InteractivePropTrackBonusTerms[0]
                    self.HpTextGenerator.setText(hpGainedStr)
                self.HpTextGenerator.clearShadow()
                self.HpTextGenerator.setAlign(TextNode.ACenter)
                if bonus == 1:
                    r = 1.0
                    g = 1.0
                    b = 0
                    a = 1
                elif bonus == 2:
                    r = 1.0
                    g = 0.5
                    b = 0
                    a = 1
                elif number < 0:
                    r = 0.9
                    g = 0
                    b = 0
                    a = 1
                else:
                    r = 0
                    g = 0.9
                    b = 0
                    a = 1
                self.HpTextGenerator.setTextColor(r, g, b, a)
                self.hpTextNode = self.HpTextGenerator.generate()
                self.hpText = self.attachNewNode(self.hpTextNode)
                self.hpText.setScale(scale)
                self.hpText.setBillboardPointEye()
                self.hpText.setBin('fixed', 100)
                self.hpText.setPos(0, 0, self.height / 2)
                seq = Sequence(self.hpText.posInterval(1.0, Point3(0, 0, self.height + 1.5), blendType='easeOut'), Wait(0.85), self.hpText.colorInterval(0.1, Vec4(r, g, b, 0)), Func(self.hideHpText))
                seq.start()

    def setAnimalSound(self, index):
        self.animalSound = index

    def setBuffs(self, buffs):
        self.buffs = buffs
        self.applyBuffs()

    def setRedeemedCodes(self, redeemedCodes):
        self.redeemedCodes = redeemedCodes

    def b_setIgnored(self, ignored):
        self.setIgnored(ignored)
        self.d_setIgnored(ignored)

    def setIgnored(self, ignored):
        self.ignored = ignored

    def d_setIgnored(self, ignored):
        self.sendUpdate('setIgnored', [ignored])

    def isIgnored(self, doId):
        return doId in self.ignored

    def addIgnore(self, doId):
        if not self.isIgnored(doId):
            self.ignored.append(doId)
            self.d_setIgnored(self.ignored)

    def removeIgnore(self, doId):
        if self.isIgnored(doId):
            self.ignored.remove(doId)
            self.d_setIgnored(self.ignored)

    def setReported(self, reported):
        self.reported = reported

    def isReported(self, doId):
        return doId in self.reported

    def addReport(self, doId):
        if not self.isReported(doId):
            self.reported.append(doId)

    def b_setTrueFriends(self, trueFriends):
        self.setTrueFriends(trueFriends)
        self.d_setTrueFriends(trueFriends)

    def setTrueFriends(self, trueFriends):
        Toon.reconsiderAllToonsUnderstandable()
        self.trueFriends = trueFriends

    def d_setTrueFriends(self, trueFriends):
        self.sendUpdate('setTrueFriends', [trueFriends])

    def isTrueFriends(self, doId):
        return doId in self.trueFriends

    def addTrueFriends(self, doId):
        if not self.isTrueFriends(doId):
            self.trueFriends.append(doId)
            self.b_setTrueFriends(self.trueFriends)

    def removeTrueFriends(self, doId):
        if self.isTrueFriends(doId):
            self.trueFriends.remove(doId)
            self.b_setTrueFriends(self.trueFriends)

    def applyBuffs(self):
        for id, timestamp in enumerate(self.buffs):
            if id == ToontownGlobals.BMovementSpeed:
                if not timestamp:
                    return
                if self.zoneId is None:
                    return
                if ZoneUtil.isDynamicZone(self.zoneId):
                    return
                if ZoneUtil.getWhereName(self.zoneId, True) not in ('playground', 'street', 'toonInterior', 'cogHQExterior', 'factoryExterior'):
                    return
                self.controlManager.setSpeeds(
                    ToontownGlobals.ToonForwardSpeed * ToontownGlobals.BMovementSpeedMultiplier,
                    ToontownGlobals.ToonJumpForce,
                    ToontownGlobals.ToonReverseSpeed * ToontownGlobals.BMovementSpeedMultiplier,
                    ToontownGlobals.ToonRotateSpeed * ToontownGlobals.BMovementSpeedMultiplier)

@magicWord(category=CATEGORY_COMMUNITY_MANAGER)
def globalTeleport():
    """
    Activates the global teleport cheat.
    """
    invoker = spellbook.getInvoker()
    invoker.sendUpdate('setTeleportOverride', [1])
    invoker.setTeleportAccess(list(ToontownGlobals.HoodsForTeleportAll))
    return 'Global teleport has been activated.'

@magicWord(category=CATEGORY_ADMINISTRATOR, types=[int])
def zone(zoneId):
    """
    Changes the invoker's zone ID.
    """
    base.cr.sendSetZoneMsg(zoneId, [zoneId])
    return 'You have been moved to zone %d.' % zoneId

@magicWord(category=CATEGORY_ADMINISTRATOR)
def blackCat():
    """
    Ask the black cat manager to turn you into a cat.
    """
    base.cr.blackCatMgr.requestBlackCatTransformation()

@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str])
def showParticle(name):
    """
    Shows a particle.
    """

    particle = BattleParticles.createParticleEffect(name)

    if particle:
        particle.start(spellbook.getTarget())
        return 'Successfully started particle!'

    return 'Particle %s does not exist.' % name