from panda3d.core import *
from toontown.toonbase.ToonBaseGlobal import *
from direct.interval.IntervalGlobal import *
from BattleBase import *
from direct.distributed.ClockDelta import *
from toontown.toonbase import ToontownBattleGlobals
from direct.distributed import DistributedNode
from direct.fsm import ClassicFSM
from direct.fsm import State
from direct.task.Task import Task
from direct.directnotify import DirectNotifyGlobal
import Movie
import MovieUtil
from toontown.suit import Suit
from direct.actor import Actor
import BattleProps
from direct.particles import ParticleEffect
import BattleParticles
from toontown.hood import ZoneUtil
from toontown.distributed import DelayDelete
from toontown.toon import TTEmote
from otp.avatar import Emote
from otp.nametag.NametagConstants import *
from otp.nametag import NametagGlobals


class DistributedBattleBase(DistributedNode.DistributedNode, BattleBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleBase')
    camPos = ToontownBattleGlobals.BattleCamDefaultPos
    camHpr = ToontownBattleGlobals.BattleCamDefaultHpr
    camFov = ToontownBattleGlobals.BattleCamDefaultFov
    camMenuFov = ToontownBattleGlobals.BattleCamMenuFov
    camJoinPos = ToontownBattleGlobals.BattleCamJoinPos
    camJoinHpr = ToontownBattleGlobals.BattleCamJoinHpr
    id = 0

    def __init__(self, cr, townBattle):
        DistributedNode.DistributedNode.__init__(self, cr)
        NodePath.__init__(self)
        self.assign(render.attachNewNode(self.uniqueBattleName('distributed-battle')))
        BattleBase.__init__(self)
        self.bossBattle = 0
        self.townBattle = townBattle
        self.__battleCleanedUp = 0
        self.activeIntervals = {}
        self.localToonJustJoined = 0
        self.choseAttackAlready = 0
        self.toons = []
        self.exitedToons = []
        self.suitTraps = ''
        self.membersKeep = None
        self.faceOffName = self.uniqueBattleName('faceoff')
        self.localToonBattleEvent = self.uniqueBattleName('localtoon-battle-event')
        self.adjustName = self.uniqueBattleName('adjust')
        self.timerCountdownTaskName = self.uniqueBattleName('timer-countdown')
        self.movie = Movie.Movie(self)
        self.timer = Timer()
        self.needAdjustTownBattle = 0
        self.streetBattle = 1
        self.levelBattle = 0
        self.localToonFsm = ClassicFSM.ClassicFSM('LocalToon', [State.State('HasLocalToon', self.enterHasLocalToon, self.exitHasLocalToon, ['NoLocalToon', 'WaitForServer']), State.State('NoLocalToon', self.enterNoLocalToon, self.exitNoLocalToon, ['HasLocalToon', 'WaitForServer']), State.State('WaitForServer', self.enterWaitForServer, self.exitWaitForServer, ['HasLocalToon', 'NoLocalToon'])], 'WaitForServer', 'WaitForServer')
        self.localToonFsm.enterInitialState()
        self.fsm = ClassicFSM.ClassicFSM('DistributedBattle', [State.State('Off', self.enterOff, self.exitOff, ['FaceOff',
          'WaitForInput',
          'WaitForJoin',
          'MakeMovie',
          'PlayMovie',
          'Reward',
          'Resume']),
         State.State('FaceOff', self.enterFaceOff, self.exitFaceOff, ['WaitForInput']),
         State.State('WaitForJoin', self.enterWaitForJoin, self.exitWaitForJoin, ['WaitForInput', 'Resume']),
         State.State('WaitForInput', self.enterWaitForInput, self.exitWaitForInput, ['WaitForInput', 'PlayMovie', 'Resume']),
         State.State('MakeMovie', self.enterMakeMovie, self.exitMakeMovie, ['PlayMovie', 'Resume']),
         State.State('PlayMovie', self.enterPlayMovie, self.exitPlayMovie, ['WaitForInput',
          'WaitForJoin',
          'Reward',
          'Resume']),
         State.State('Reward', self.enterReward, self.exitReward, ['Resume']),
         State.State('Resume', self.enterResume, self.exitResume, [])], 'Off', 'Off')
        self.fsm.enterInitialState()
        self.adjustFsm = ClassicFSM.ClassicFSM('Adjust', [State.State('Adjusting', self.enterAdjusting, self.exitAdjusting, ['NotAdjusting']), State.State('NotAdjusting', self.enterNotAdjusting, self.exitNotAdjusting, ['Adjusting'])], 'NotAdjusting', 'NotAdjusting')
        self.adjustFsm.enterInitialState()
        self.interactiveProp = None
        self.fireCount = 0
        return

    def uniqueBattleName(self, name):
        DistributedBattleBase.id += 1
        return name + '-%d' % DistributedBattleBase.id

    def generate(self):
        self.notify.debug('generate(%s)' % self.doId)
        DistributedNode.DistributedNode.generate(self)
        self.__battleCleanedUp = 0
        self.reparentTo(render)
        self._skippingRewardMovie = False

    def storeInterval(self, interval, name):
        if name in self.activeIntervals:
            ival = self.activeIntervals[name]
            if hasattr(ival, 'delayDelete') or hasattr(ival, 'delayDeletes'):
                self.clearInterval(name, finish=1)
        self.activeIntervals[name] = interval

    def __cleanupIntervals(self):
        for interval in self.activeIntervals.values():
            interval.finish()
            DelayDelete.cleanupDelayDeletes(interval)

        self.activeIntervals = {}

    def clearInterval(self, name, finish = 0):
        if name in self.activeIntervals:
            ival = self.activeIntervals[name]
            if finish:
                ival.finish()
            else:
                ival.pause()
            if name in self.activeIntervals:
                DelayDelete.cleanupDelayDeletes(ival)
                if name in self.activeIntervals:
                    del self.activeIntervals[name]
        else:
            self.notify.debug('interval: %s already cleared' % name)

    def finishInterval(self, name):
        if name in self.activeIntervals:
            interval = self.activeIntervals[name]
            interval.finish()

    def disable(self):
        self.notify.debug('disable(%s)' % self.doId)
        self.cleanupBattle()
        DistributedNode.DistributedNode.disable(self)

    def battleCleanedUp(self):
        return self.__battleCleanedUp

    def cleanupBattle(self):
        if self.__battleCleanedUp:
            return
        self.notify.debug('cleanupBattle(%s)' % self.doId)
        self.__battleCleanedUp = 1
        self.__cleanupIntervals()
        self.fsm.requestFinalState()
        if self.hasLocalToon():
            self.removeLocalToon()
            base.camLens.setMinFov(settings['fov']/(4./3.))
        self.localToonFsm.request('WaitForServer')
        self.ignoreAll()
        for suit in self.suits:
            if suit.battleTrap != NO_TRAP:
                self.notify.debug('250 calling self.removeTrap, suit=%d' % suit.doId)
                self.removeTrap(suit)
            suit.battleTrap = NO_TRAP
            suit.battleTrapProp = None
            self.notify.debug('253 suit.battleTrapProp = None')
            suit.battleTrapIsFresh = 0

        self.suits = []
        self.pendingSuits = []
        self.joiningSuits = []
        self.activeSuits = []
        self.suitTraps = ''
        self.toons = []
        self.joiningToons = []
        self.pendingToons = []
        self.activeToons = []
        self.runningToons = []
        self.__stopTimer()
        self.__cleanupIntervals()
        self._removeMembersKeep()
        return

    def delete(self):
        self.notify.debug('delete(%s)' % self.doId)
        self.__cleanupIntervals()
        self._removeMembersKeep()
        self.movie.cleanup()
        del self.townBattle
        self.removeNode()
        self.fsm = None
        self.localToonFsm = None
        self.adjustFsm = None
        self.__stopTimer()
        self.timer = None
        DistributedNode.DistributedNode.delete(self)
        return

    def loadTrap(self, suit, trapid):
        self.notify.debug('loadTrap() trap: %d suit: %d' % (trapid, suit.doId))
        trapName = AvProps[TRAP][trapid]
        trap = BattleProps.globalPropPool.getProp(trapName)
        suit.battleTrap = trapid
        suit.battleTrapIsFresh = 0
        suit.battleTrapProp = trap
        self.notify.debug('suit.battleTrapProp = trap %s' % trap)
        if trap.getName() == 'traintrack':
            pass
        else:
            trap.wrtReparentTo(suit)
        distance = MovieUtil.SUIT_TRAP_DISTANCE
        if trapName == 'rake':
            distance = MovieUtil.SUIT_TRAP_RAKE_DISTANCE
            distance += MovieUtil.getSuitRakeOffset(suit)
            trap.setH(180)
            trap.setScale(0.7)
        elif trapName == 'trapdoor' or trapName == 'quicksand':
            trap.setScale(1.7)
        elif trapName == 'marbles':
            distance = MovieUtil.SUIT_TRAP_MARBLES_DISTANCE
            trap.setH(94)
        elif trapName == 'tnt':
            trap.setP(90)
            tip = trap.find('**/joint_attachEmitter')
            sparks = BattleParticles.createParticleEffect(file='tnt')
            trap.sparksEffect = sparks
            sparks.start(tip)
        trap.setPos(0, distance, 0)
        if isinstance(trap, Actor.Actor):
            frame = trap.getNumFrames(trapName) - 1
            trap.pose(trapName, frame)

    def removeTrap(self, suit, removeTrainTrack = False):
        self.notify.debug('removeTrap() from suit: %d, removeTrainTrack=%s' % (suit.doId, removeTrainTrack))
        if suit.battleTrapProp == None:
            self.notify.debug('suit.battleTrapProp == None, suit.battleTrap=%s setting to NO_TRAP, returning' % suit.battleTrap)
            suit.battleTrap = NO_TRAP
            return
        if suit.battleTrap == UBER_GAG_LEVEL_INDEX:
            if removeTrainTrack:
                self.notify.debug('doing removeProp on traintrack')
                MovieUtil.removeProp(suit.battleTrapProp)
                for otherSuit in self.suits:
                    if not otherSuit == suit:
                        otherSuit.battleTrapProp = None
                        self.notify.debug('351 otherSuit=%d otherSuit.battleTrapProp = None' % otherSuit.doId)
                        otherSuit.battleTrap = NO_TRAP
                        otherSuit.battleTrapIsFresh = 0

            else:
                self.notify.debug('deliberately not doing removeProp on traintrack')
        else:
            self.notify.debug('suit.battleTrap != UBER_GAG_LEVEL_INDEX')
            MovieUtil.removeProp(suit.battleTrapProp)
        suit.battleTrapProp = None
        self.notify.debug('360 suit.battleTrapProp = None')
        suit.battleTrap = NO_TRAP
        suit.battleTrapIsFresh = 0
        return

    def pause(self):
        self.timer.stop()

    def unpause(self):
        self.timer.resume()

    def findSuit(self, id):
        for s in self.suits:
            if s.doId == id:
                return s

        return None

    def findToon(self, id):
        toon = self.getToon(id)
        if toon == None:
            return
        for t in self.toons:
            if t == toon:
                return t

        return

    def isSuitLured(self, suit):
        if self.luredSuits.count(suit) != 0:
            return 1
        return 0

    def unlureSuit(self, suit):
        self.notify.debug('movie unluring suit %s' % suit.doId)
        if self.luredSuits.count(suit) != 0:
            self.luredSuits.remove(suit)
            self.needAdjustTownBattle = 1
        return None

    def lureSuit(self, suit):
        self.notify.debug('movie luring suit %s' % suit.doId)
        if self.luredSuits.count(suit) == 0:
            self.luredSuits.append(suit)
            self.needAdjustTownBattle = 1
        return None

    def getActorPosHpr(self, actor, actorList = []):
        if isinstance(actor, Suit.Suit):
            if actorList == []:
                actorList = self.activeSuits
            if actorList.count(actor) != 0:
                numSuits = len(actorList) - 1
                index = actorList.index(actor)
                point = self.suitPoints[numSuits][index]
                return (Point3(point[0]), VBase3(point[1], 0.0, 0.0))
            else:
                self.notify.warning('getActorPosHpr() - suit not active')
        else:
            if actorList == []:
                actorList = self.activeToons
            if actorList.count(actor) != 0:
                numToons = len(actorList) - 1
                index = actorList.index(actor)
                point = self.toonPoints[numToons][index]
                return (Point3(point[0]), VBase3(point[1], 0.0, 0.0))
            else:
                self.notify.warning('getActorPosHpr() - toon not active')

    def setLevelDoId(self, levelDoId):
        pass

    def setBattleCellId(self, battleCellId):
        pass

    def getInteractiveProp(self):
        if config.GetBool('want-anim-props', True):
            if self.interactiveProp:
                return self.interactiveProp
            elif base.cr.playGame.hood and hasattr(base.cr.playGame.hood, 'loader'):
                loader = base.cr.playGame.hood.loader

                if hasattr(loader, 'getInteractiveProp'):
                    self.interactiveProp = base.cr.playGame.hood.loader.getInteractiveProp(self.zoneId)

                    return self.interactiveProp
            return None
        else:
            return None

    def getInteractivePropTrackBonus(self):
        prop = self.getInteractiveProp()

        return prop.BattleTrack if prop else -1

    def setPosition(self, x, y, z):
        self.notify.debug('setPosition() - %d %d %d' % (x, y, z))
        pos = Point3(x, y, z)
        self.setPos(pos)

    def setInitialSuitPos(self, x, y, z):
        self.initialSuitPos = Point3(x, y, z)
        self.headsUp(self.initialSuitPos)

    def setZoneId(self, zoneId):
        self.zoneId = zoneId

    def setBossBattle(self, value):
        self.bossBattle = value

    def setState(self, state, timestamp):
        if self.__battleCleanedUp:
            return
        self.notify.debug('setState(%s)' % state)
        self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])

    def setMembers(self, suits, suitsJoining, suitsPending, suitsActive, suitsLured, suitTraps, toons, toonsJoining, toonsPending, toonsActive, toonsRunning, timestamp):
        if self.__battleCleanedUp:
            return
        self.notify.debug('setMembers() - suits: %s suitsJoining: %s suitsPending: %s suitsActive: %s suitsLured: %s suitTraps: %s toons: %s toonsJoining: %s toonsPending: %s toonsActive: %s toonsRunning: %s' % (suits,
         suitsJoining,
         suitsPending,
         suitsActive,
         suitsLured,
         suitTraps,
         toons,
         toonsJoining,
         toonsPending,
         toonsActive,
         toonsRunning))
        ts = globalClockDelta.localElapsedTime(timestamp)
        oldsuits = self.suits
        self.suits = []
        suitGone = 0
        prop = self.getInteractiveProp()

        for s in suits:
            if s in self.cr.doId2do:
                suit = self.cr.doId2do[s]
                suit.setState('Battle')
                self.suits.append(suit)

                if prop:
                    suit.interactivePropTrackBonus = prop.BattleTrack

                try:
                    suit.battleTrap
                except:
                    suit.battleTrap = NO_TRAP
                    suit.battleTrapProp = None
                    self.notify.debug('496 suit.battleTrapProp = None')
                    suit.battleTrapIsFresh = 0

            else:
                self.notify.warning('setMembers() - no suit in repository: %d' % s)
                self.suits.append(None)
                suitGone = 1

        numSuitsThatDied = 0
        for s in oldsuits:
            if self.suits.count(s) == 0:
                self.__removeSuit(s)
                numSuitsThatDied += 1
                self.notify.debug('suit %d dies, numSuitsThatDied=%d' % (s.doId, numSuitsThatDied))

        if numSuitsThatDied == 4:
            trainTrap = self.find('**/traintrack')
            if not trainTrap.isEmpty():
                self.notify.debug('removing old train trap when 4 suits died')
                trainTrap.removeNode()
        for s in suitsJoining:
            suit = self.suits[int(s)]
            if suit != None and self.joiningSuits.count(suit) == 0:
                self.makeSuitJoin(suit, ts)

        for s in suitsPending:
            suit = self.suits[int(s)]
            if suit != None and self.pendingSuits.count(suit) == 0:
                self.__makeSuitPending(suit)

        activeSuits = []
        for s in suitsActive:
            suit = self.suits[int(s)]
            if suit != None and self.activeSuits.count(suit) == 0:
                activeSuits.append(suit)

        oldLuredSuits = self.luredSuits
        self.luredSuits = []
        for s in suitsLured:
            suit = self.suits[int(s)]
            if suit != None:
                self.luredSuits.append(suit)
                if oldLuredSuits.count(suit) == 0:
                    self.needAdjustTownBattle = 1

        if self.needAdjustTownBattle == 0:
            for s in oldLuredSuits:
                if self.luredSuits.count(s) == 0:
                    self.needAdjustTownBattle = 1

        index = 0
        oldSuitTraps = self.suitTraps
        self.suitTraps = suitTraps
        for s in suitTraps:
            trapid = int(s)
            if trapid == 9:
                trapid = -1
            suit = self.suits[index]
            index += 1
            if suit != None:
                if (trapid == NO_TRAP or trapid != suit.battleTrap) and suit.battleTrapProp != None:
                    self.notify.debug('569 calling self.removeTrap, suit=%d' % suit.doId)
                    self.removeTrap(suit)
                if trapid != NO_TRAP and suit.battleTrapProp == None:
                    if self.fsm.getCurrentState().getName() != 'PlayMovie':
                        self.loadTrap(suit, trapid)

        if len(oldSuitTraps) != len(self.suitTraps):
            self.needAdjustTownBattle = 1
        else:
            for i in xrange(len(oldSuitTraps)):
                if oldSuitTraps[i] == '9' and self.suitTraps[i] != '9' or oldSuitTraps[i] != '9' and self.suitTraps[i] == '9':
                    self.needAdjustTownBattle = 1
                    break

        if suitGone:
            validSuits = []
            for s in self.suits:
                if s != None:
                    validSuits.append(s)

            self.suits = validSuits
            self.needAdjustTownBattle = 1
        oldtoons = self.toons
        self.toons = []
        toonGone = 0
        for t in toons:
            toon = self.getToon(t)
            if toon == None:
                self.notify.warning('setMembers() - toon not in cr!')
                self.toons.append(None)
                toonGone = 1
                continue
            self.toons.append(toon)
            if oldtoons.count(toon) == 0:
                self.notify.debug('setMembers() - add toon: %d' % toon.doId)
                self.__listenForUnexpectedExit(toon)
                toon.stopLookAround()
                toon.stopSmooth()

        for t in oldtoons:
            if self.toons.count(t) == 0:
                if self.__removeToon(t) == 1:
                    self.notify.debug('setMembers() - local toon left battle')
                    return []

        for t in toonsJoining:
            if int(t) < len(self.toons):
                toon = self.toons[int(t)]
                if toon != None and self.joiningToons.count(toon) == 0:
                    self.__makeToonJoin(toon, toonsPending, ts)
            else:
                self.notify.warning('setMembers toonsJoining t=%s not in self.toons %s' % (t, self.toons))

        for t in toonsPending:
            if int(t) < len(self.toons):
                toon = self.toons[int(t)]
                if toon != None and self.pendingToons.count(toon) == 0:
                    self.__makeToonPending(toon, ts)
            else:
                self.notify.warning('setMembers toonsPending t=%s not in self.toons %s' % (t, self.toons))

        for t in toonsRunning:
            toon = self.toons[int(t)]
            if toon != None and self.runningToons.count(toon) == 0:
                self.__makeToonRun(toon, ts)

        activeToons = []
        for t in toonsActive:
            toon = self.toons[int(t)]
            if toon != None and self.activeToons.count(toon) == 0:
                activeToons.append(toon)

        if len(activeSuits) > 0 or len(activeToons) > 0:
            self.__makeAvsActive(activeSuits, activeToons)
        if toonGone == 1:
            validToons = []
            for toon in self.toons:
                if toon != None:
                    validToons.append(toon)

            self.toons = validToons
        if len(self.activeToons) > 0:
            self.__requestAdjustTownBattle()
        currStateName = self.localToonFsm.getCurrentState().getName()
        if self.toons.count(base.localAvatar):
            if oldtoons.count(base.localAvatar) == 0:
                self.notify.debug('setMembers() - local toon just joined')
                if self.streetBattle == 1:
                    base.cr.playGame.getPlace().enterZone(self.zoneId)
                self.localToonJustJoined = 1
            if currStateName != 'HasLocalToon':
                self.localToonFsm.request('HasLocalToon')
        else:
            if oldtoons.count(base.localAvatar):
                self.notify.debug('setMembers() - local toon just ran')
                if self.levelBattle:
                    self.unlockLevelViz()
            if currStateName != 'NoLocalToon':
                self.localToonFsm.request('NoLocalToon')
        for suit in self.luredSuits:
            suit.loop('lured')
        return oldtoons

    def adjust(self, timestamp):
        if self.__battleCleanedUp:
            return
        self.notify.debug('adjust(%f) from server' % globalClockDelta.localElapsedTime(timestamp))
        self.adjustFsm.request('Adjusting', [globalClockDelta.localElapsedTime(timestamp)])

    def setMovie(self, active, toons, suits, id0, tr0, le0, tg0, hp0, ac0, hpb0, kbb0, died0, revive0, id1, tr1, le1, tg1, hp1, ac1, hpb1, kbb1, died1, revive1, id2, tr2, le2, tg2, hp2, ac2, hpb2, kbb2, died2, revive2, id3, tr3, le3, tg3, hp3, ac3, hpb3, kbb3, died3, revive3, sid0, at0, stg0, dm0, sd0, sb0, st0, sid1, at1, stg1, dm1, sd1, sb1, st1, sid2, at2, stg2, dm2, sd2, sb2, st2, sid3, at3, stg3, dm3, sd3, sb3, st3):
        if self.__battleCleanedUp:
            return
        self.notify.debug('setMovie()')
        if int(active) == 1:
            self.notify.debug('setMovie() - movie is active')
            self.movie.genAttackDicts(toons, suits, id0, tr0, le0, tg0, hp0, ac0, hpb0, kbb0, died0, revive0, id1, tr1, le1, tg1, hp1, ac1, hpb1, kbb1, died1, revive1, id2, tr2, le2, tg2, hp2, ac2, hpb2, kbb2, died2, revive2, id3, tr3, le3, tg3, hp3, ac3, hpb3, kbb3, died3, revive3, sid0, at0, stg0, dm0, sd0, sb0, st0, sid1, at1, stg1, dm1, sd1, sb1, st1, sid2, at2, stg2, dm2, sd2, sb2, st2, sid3, at3, stg3, dm3, sd3, sb3, st3)

    def setChosenToonAttacks(self, ids, tracks, levels, targets):
        if self.__battleCleanedUp:
            return
        self.notify.debug('setChosenToonAttacks() - (%s), (%s), (%s), (%s)' % (ids,
         tracks,
         levels,
         targets))
        toonIndices = []
        targetIndices = []
        unAttack = 0
        localToonInList = 0
        for i in xrange(len(ids)):
            track = tracks[i]
            level = levels[i]
            toon = self.findToon(ids[i])
            if toon == None or self.activeToons.count(toon) == 0:
                self.notify.warning('setChosenToonAttacks() - toon gone or not in battle: %d!' % ids[i])
                toonIndices.append(-1)
                tracks.append(-1)
                levels.append(-1)
                targetIndices.append(-1)
                continue
            if toon == base.localAvatar:
                localToonInList = 1
            toonIndices.append(self.activeToons.index(toon))
            if track == SOS:
                targetIndex = -1
            elif track == NPCSOS:
                targetIndex = targets[i]
            elif track == PETSOS:
                targetIndex = -1
            elif track == PASS:
                targetIndex = -1
                tracks[i] = PASS_ATTACK
            elif attackAffectsGroup(track, level):
                targetIndex = -1
            elif track == HEAL:
                target = self.findToon(targets[i])
                if target != None and self.activeToons.count(target) != 0:
                    targetIndex = self.activeToons.index(target)
                else:
                    targetIndex = -1
            elif track == UN_ATTACK:
                targetIndex = -1
                tracks[i] = NO_ATTACK
                if toon == base.localAvatar:
                    unAttack = 1
                    self.choseAttackAlready = 0
            elif track == NO_ATTACK:
                targetIndex = -1
            else:
                target = self.findSuit(targets[i])
                if target != None and self.activeSuits.count(target) != 0:
                    targetIndex = self.activeSuits.index(target)
                else:
                    targetIndex = -1
            targetIndices.append(targetIndex)

        for i in xrange(4 - len(ids)):
            toonIndices.append(-1)
            tracks.append(-1)
            levels.append(-1)
            targetIndices.append(-1)

        self.townBattleAttacks = (toonIndices,
         tracks,
         levels,
         targetIndices)
        if self.localToonActive() and localToonInList == 1:
            if unAttack == 1 and self.fsm.getCurrentState().getName() == 'WaitForInput':
                if self.townBattle.fsm.getCurrentState().getName() != 'Attack':
                    self.townBattle.setState('Attack')
            self.townBattle.updateChosenAttacks(self.townBattleAttacks[0], self.townBattleAttacks[1], self.townBattleAttacks[2], self.townBattleAttacks[3])
        return

    def setBattleExperience(self, id0, origExp0, earnedExp0, origQuests0, items0, missedItems0, origMerits0, merits0, parts0, id1, origExp1, earnedExp1, origQuests1, items1, missedItems1, origMerits1, merits1, parts1, id2, origExp2, earnedExp2, origQuests2, items2, missedItems2, origMerits2, merits2, parts2, id3, origExp3, earnedExp3, origQuests3, items3, missedItems3, origMerits3, merits3, parts3, deathList, uberList, helpfulToonsList):
        if self.__battleCleanedUp:
            return
        self.movie.genRewardDicts(id0, origExp0, earnedExp0, origQuests0, items0, missedItems0, origMerits0, merits0, parts0, id1, origExp1, earnedExp1, origQuests1, items1, missedItems1, origMerits1, merits1, parts1, id2, origExp2, earnedExp2, origQuests2, items2, missedItems2, origMerits2, merits2, parts2, id3, origExp3, earnedExp3, origQuests3, items3, missedItems3, origMerits3, merits3, parts3, deathList, uberList, helpfulToonsList)

    def __listenForUnexpectedExit(self, toon):
        self.accept(toon.uniqueName('disable'), self.__handleUnexpectedExit, extraArgs=[toon])
        self.accept(toon.uniqueName('died'), self.__handleDied, extraArgs=[toon])

    def __handleUnexpectedExit(self, toon):
        self.notify.warning('handleUnexpectedExit() - toon: %d' % toon.doId)
        self.__removeToon(toon, unexpected=1)

    def __handleDied(self, toon):
        self.notify.warning('handleDied() - toon: %d' % toon.doId)
        if toon == base.localAvatar:
            self.d_toonDied(toon.doId)
            self.cleanupBattle()

    def delayDeleteMembers(self):
        membersKeep = []
        for t in self.toons:
            membersKeep.append(DelayDelete.DelayDelete(t, 'delayDeleteMembers'))

        for s in self.suits:
            membersKeep.append(DelayDelete.DelayDelete(s, 'delayDeleteMembers'))

        self._removeMembersKeep()
        self.membersKeep = membersKeep

    def _removeMembersKeep(self):
        if self.membersKeep:
            for delayDelete in self.membersKeep:
                delayDelete.destroy()

        self.membersKeep = None
        return

    def __removeSuit(self, suit):
        self.notify.debug('__removeSuit(%d)' % suit.doId)
        if self.suits.count(suit) != 0:
            self.suits.remove(suit)
        if self.joiningSuits.count(suit) != 0:
            self.joiningSuits.remove(suit)
        if self.pendingSuits.count(suit) != 0:
            self.pendingSuits.remove(suit)
        if self.activeSuits.count(suit) != 0:
            self.activeSuits.remove(suit)
        self.suitGone = 1
        if suit.battleTrap != NO_TRAP:
            self.notify.debug('882 calling self.removeTrap, suit=%d' % suit.doId)
            self.removeTrap(suit)
        suit.battleTrap = NO_TRAP
        suit.battleTrapProp = None
        self.notify.debug('883 suit.battleTrapProp = None')
        suit.battleTrapIsFresh = 0
        return

    def __removeToon(self, toon, unexpected = 0):
        self.notify.debug('__removeToon(%d)' % toon.doId)
        self.exitedToons.append(toon)
        if self.toons.count(toon) != 0:
            self.toons.remove(toon)
        if self.joiningToons.count(toon) != 0:
            self.clearInterval(self.taskName('to-pending-toon-%d' % toon.doId))
            if toon in self.joiningToons:
                self.joiningToons.remove(toon)
        if self.pendingToons.count(toon) != 0:
            self.pendingToons.remove(toon)
        if self.activeToons.count(toon) != 0:
            self.activeToons.remove(toon)
        if self.runningToons.count(toon) != 0:
            self.clearInterval(self.taskName('running-%d' % toon.doId), finish=1)
            if toon in self.runningToons:
                self.runningToons.remove(toon)
        self.ignore(toon.uniqueName('disable'))
        self.ignore(toon.uniqueName('died'))
        self.toonGone = 1
        if toon == base.localAvatar:
            self.removeLocalToon()
            self.__teleportToSafeZone(toon)
            return 1
        return 0

    def removeLocalToon(self):
        if self._skippingRewardMovie:
            return
        if base.cr.playGame.getPlace() != None:
            base.cr.playGame.getPlace().setState('walk')
        base.localAvatar.earnedExperience = None
        self.localToonFsm.request('NoLocalToon')
        return

    def removeInactiveLocalToon(self, toon):
        self.notify.debug('removeInactiveLocalToon(%d)' % toon.doId)
        self.exitedToons.append(toon)
        if self.toons.count(toon) != 0:
            self.toons.remove(toon)
        if self.joiningToons.count(toon) != 0:
            self.clearInterval(self.taskName('to-pending-toon-%d' % toon.doId), finish=1)
            if toon in self.joiningToons:
                self.joiningToons.remove(toon)
        if self.pendingToons.count(toon) != 0:
            self.pendingToons.remove(toon)
        self.ignore(toon.uniqueName('disable'))
        self.ignore(toon.uniqueName('died'))
        base.cr.playGame.getPlace().setState('walk')
        self.localToonFsm.request('WaitForServer')

    def __createJoinInterval(self, av, destPos, destHpr, name, ts, callback, toon = 0):
        joinTrack = Sequence()
        joinTrack.append(Func(Emote.globalEmote.disableAll, av, 'dbattlebase, createJoinInterval'))
        avPos = av.getPos(self)
        avPos = Point3(avPos[0], avPos[1], 0.0)
        av.setShadowHeight(0)
        plist = self.buildJoinPointList(avPos, destPos, toon)
        if len(plist) == 0:
            joinTrack.append(Func(av.headsUp, self, destPos))
            if toon == 0:
                timeToDest = self.calcSuitMoveTime(avPos, destPos)
                joinTrack.append(Func(av.loop, 'walk'))
            else:
                timeToDest = self.calcToonMoveTime(avPos, destPos)
                joinTrack.append(Func(av.loop, 'run'))
            if timeToDest > BATTLE_SMALL_VALUE:
                joinTrack.append(LerpPosInterval(av, timeToDest, destPos, other=self))
                totalTime = timeToDest
            else:
                totalTime = 0
        else:
            timeToPerimeter = 0
            if toon == 0:
                timeToPerimeter = self.calcSuitMoveTime(plist[0], avPos)
                timePerSegment = 10.0 / BattleBase.suitSpeed
                timeToDest = self.calcSuitMoveTime(BattleBase.posA, destPos)
            else:
                timeToPerimeter = self.calcToonMoveTime(plist[0], avPos)
                timePerSegment = 10.0 / BattleBase.toonSpeed
                timeToDest = self.calcToonMoveTime(BattleBase.posE, destPos)
            totalTime = timeToPerimeter + (len(plist) - 1) * timePerSegment + timeToDest
            if totalTime > MAX_JOIN_T:
                self.notify.warning('__createJoinInterval() - time: %f' % totalTime)
            joinTrack.append(Func(av.headsUp, self, plist[0]))
            if toon == 0:
                joinTrack.append(Func(av.loop, 'walk'))
            else:
                joinTrack.append(Func(av.loop, 'run'))
            joinTrack.append(LerpPosInterval(av, timeToPerimeter, plist[0], other=self))
            for p in plist[1:]:
                joinTrack.append(Func(av.headsUp, self, p))
                joinTrack.append(LerpPosInterval(av, timePerSegment, p, other=self))

            joinTrack.append(Func(av.headsUp, self, destPos))
            joinTrack.append(LerpPosInterval(av, timeToDest, destPos, other=self))
        joinTrack.append(Func(av.loop, 'neutral'))
        joinTrack.append(Func(av.headsUp, self, Point3(0, 0, 0)))
        tval = totalTime - ts
        if tval < 0:
            tval = totalTime
        joinTrack.append(Func(Emote.globalEmote.releaseAll, av, 'dbattlebase, createJoinInterval'))
        joinTrack.append(Func(callback, av, tval))
        if av == base.localAvatar:
            camTrack = Sequence()

            def setCamFov(fov):
                base.camLens.setMinFov(fov/(4./3.))

            camTrack.append(Func(setCamFov, self.camFov))
            camTrack.append(Func(camera.wrtReparentTo, self))
            camTrack.append(Func(camera.setPos, self.camJoinPos))
            camTrack.append(Func(camera.setHpr, self.camJoinHpr))
            return Parallel(joinTrack, camTrack, name=name)
        else:
            return Sequence(joinTrack, name=name)

    def makeSuitJoin(self, suit, ts):
        self.notify.debug('makeSuitJoin(%d)' % suit.doId)
        spotIndex = len(self.pendingSuits) + len(self.joiningSuits)
        self.joiningSuits.append(suit)
        suit.setState('Battle')
        openSpot = self.suitPendingPoints[spotIndex]
        pos = openSpot[0]
        hpr = VBase3(openSpot[1], 0.0, 0.0)
        trackName = self.taskName('to-pending-suit-%d' % suit.doId)
        track = self.__createJoinInterval(suit, pos, hpr, trackName, ts, self.__handleSuitJoinDone)
        track.start(ts)
        track.delayDelete = DelayDelete.DelayDelete(suit, 'makeSuitJoin')
        self.storeInterval(track, trackName)
        if ToontownBattleGlobals.SkipMovie:
            track.finish()

    def __handleSuitJoinDone(self, suit, ts):
        self.notify.debug('suit: %d is now pending' % suit.doId)
        if self.hasLocalToon():
            self.d_joinDone(base.localAvatar.doId, suit.doId)

    def __makeSuitPending(self, suit):
        self.notify.debug('__makeSuitPending(%d)' % suit.doId)
        self.clearInterval(self.taskName('to-pending-suit-%d' % suit.doId), finish=1)
        if self.joiningSuits.count(suit):
            self.joiningSuits.remove(suit)
        self.pendingSuits.append(suit)

    def __teleportToSafeZone(self, toon):
        self.notify.debug('teleportToSafeZone(%d)' % toon.doId)
        hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
        if hoodId in base.localAvatar.hoodsVisited:
            target_sz = ZoneUtil.getSafeZoneId(self.zoneId)
        else:
            target_sz = ZoneUtil.getSafeZoneId(base.localAvatar.defaultZone)
        base.cr.playGame.getPlace().fsm.request('teleportOut', [{'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 __makeToonJoin(self, toon, pendingToons, ts):
        self.notify.debug('__makeToonJoin(%d)' % toon.doId)
        spotIndex = len(pendingToons) + len(self.joiningToons)
        self.joiningToons.append(toon)
        openSpot = self.toonPendingPoints[spotIndex]
        pos = openSpot[0]
        hpr = VBase3(openSpot[1], 0.0, 0.0)
        trackName = self.taskName('to-pending-toon-%d' % toon.doId)
        track = self.__createJoinInterval(toon, pos, hpr, trackName, ts, self.__handleToonJoinDone, toon=1)
        if toon != base.localAvatar:
            toon.animFSM.request('off')
        track.start(ts)
        track.delayDelete = DelayDelete.DelayDelete(toon, '__makeToonJoin')
        self.storeInterval(track, trackName)

    def __handleToonJoinDone(self, toon, ts):
        self.notify.debug('__handleToonJoinDone() - pending: %d' % toon.doId)
        if self.hasLocalToon():
            self.d_joinDone(base.localAvatar.doId, toon.doId)

    def __makeToonPending(self, toon, ts):
        self.notify.debug('__makeToonPending(%d)' % toon.doId)
        self.clearInterval(self.taskName('to-pending-toon-%d' % toon.doId), finish=1)
        if self.joiningToons.count(toon):
            self.joiningToons.remove(toon)
        spotIndex = len(self.pendingToons)
        self.pendingToons.append(toon)
        openSpot = self.toonPendingPoints[spotIndex]
        pos = openSpot[0]
        hpr = VBase3(openSpot[1], 0.0, 0.0)
        toon.loop('neutral')
        toon.setPosHpr(self, pos, hpr)
        if base.localAvatar == toon:
            currStateName = self.fsm.getCurrentState().getName()

    def __makeAvsActive(self, suits, toons):
        self.notify.debug('__makeAvsActive()')
        self.__stopAdjusting()
        for s in suits:
            if self.joiningSuits.count(s):
                self.notify.warning('suit: %d was in joining list!' % s.doId)
                self.joiningSuits.remove(s)
            if self.pendingSuits.count(s):
                self.pendingSuits.remove(s)
            self.notify.debug('__makeAvsActive() - suit: %d' % s.doId)
            self.activeSuits.append(s)

        if len(self.activeSuits) >= 1:
            for suit in self.activeSuits:
                suitPos, suitHpr = self.getActorPosHpr(suit)
                if self.isSuitLured(suit) == 0:
                    suit.setPosHpr(self, suitPos, suitHpr)
                else:
                    spos = Point3(suitPos[0], suitPos[1] - MovieUtil.SUIT_LURE_DISTANCE, suitPos[2])
                    suit.setPosHpr(self, spos, suitHpr)
                suit.loop('neutral')

        for toon in toons:
            if self.joiningToons.count(toon):
                self.notify.warning('toon: %d was in joining list!' % toon.doId)
                self.joiningToons.remove(toon)
            if self.pendingToons.count(toon):
                self.pendingToons.remove(toon)
            self.notify.debug('__makeAvsActive() - toon: %d' % toon.doId)
            if self.activeToons.count(toon) == 0:
                self.activeToons.append(toon)
            else:
                self.notify.warning('makeAvsActive() - toon: %d is active!' % toon.doId)

        if len(self.activeToons) >= 1:
            for toon in self.activeToons:
                toonPos, toonHpr = self.getActorPosHpr(toon)
                toon.setPosHpr(self, toonPos, toonHpr)
                toon.loop('neutral')

        if self.fsm.getCurrentState().getName() == 'WaitForInput' and self.localToonActive() and self.localToonJustJoined == 1:
            self.notify.debug('makeAvsActive() - local toon just joined')
            self.__enterLocalToonWaitForInput()
            self.localToonJustJoined = 0
            self.startTimer()

    def __makeToonRun(self, toon, ts):
        self.notify.debug('__makeToonRun(%d)' % toon.doId)
        if self.activeToons.count(toon):
            self.activeToons.remove(toon)
        self.runningToons.append(toon)
        self.toonGone = 1
        self.__stopTimer()
        if self.localToonRunning():
            self.townBattle.setState('Off')
        runMTrack = MovieUtil.getToonTeleportOutInterval(toon)
        runName = self.taskName('running-%d' % toon.doId)
        self.notify.debug('duration: %f' % runMTrack.getDuration())
        runMTrack.start(ts)
        runMTrack.delayDelete = DelayDelete.DelayDelete(toon, '__makeToonRun')
        self.storeInterval(runMTrack, runName)

    def getToon(self, toonId):
        if toonId in self.cr.doId2do:
            return self.cr.doId2do[toonId]
        else:
            self.notify.warning('getToon() - toon: %d not in repository!' % toonId)
            return None
        return None

    def d_toonRequestJoin(self, toonId, pos):
        self.notify.debug('network:toonRequestJoin()')
        self.sendUpdate('toonRequestJoin', [pos[0], pos[1], pos[2]])

    def d_toonRequestRun(self, toonId):
        self.notify.debug('network:toonRequestRun()')
        self.sendUpdate('toonRequestRun', [])

    def d_toonDied(self, toonId):
        self.notify.debug('network:toonDied()')
        self.sendUpdate('toonDied', [])

    def d_faceOffDone(self, toonId):
        self.notify.debug('network:faceOffDone()')
        self.sendUpdate('faceOffDone', [])

    def d_adjustDone(self, toonId):
        self.notify.debug('network:adjustDone()')
        self.sendUpdate('adjustDone', [])

    def d_timeout(self, toonId):
        self.notify.debug('network:timeout()')
        self.sendUpdate('timeout', [])

    def d_movieDone(self, toonId):
        self.notify.debug('network:movieDone()')
        self.sendUpdate('movieDone', [])

    def d_rewardDone(self, toonId):
        self.notify.debug('network:rewardDone()')
        self.sendUpdate('rewardDone', [])

    def d_joinDone(self, toonId, avId):
        self.notify.debug('network:joinDone(%d)' % avId)
        self.sendUpdate('joinDone', [avId])

    def d_requestAttack(self, toonId, track, level, av):
        self.notify.debug('network:requestAttack(%d, %d, %d)' % (track, level, av))
        self.sendUpdate('requestAttack', [track, level, av])

    def d_requestPetProxy(self, toonId, av):
        self.notify.debug('network:requestPetProxy(%s)' % av)
        self.sendUpdate('requestPetProxy', [av])

    def enterOff(self, ts = 0):
        self.localToonFsm.requestFinalState()
        return None

    def exitOff(self):
        return None

    def enterFaceOff(self, ts = 0):
        return None

    def exitFaceOff(self):
        return None

    def enterWaitForJoin(self, ts = 0):
        self.notify.debug('enterWaitForJoin()')
        return None

    def exitWaitForJoin(self):
        return None

    def __enterLocalToonWaitForInput(self):
        self.notify.debug('enterLocalToonWaitForInput()')
        camera.setPosHpr(self.camPos, self.camHpr)
        base.camLens.setMinFov(self.camMenuFov/(4./3.))
        NametagGlobals.setMasterArrowsOn(0)
        self.townBattle.setState('Attack')
        self.accept(self.localToonBattleEvent, self.__handleLocalToonBattleEvent)

    def startTimer(self, ts = 0):
        self.notify.debug('startTimer()')
        if ts >= CLIENT_INPUT_TIMEOUT:
            self.notify.warning('startTimer() - ts: %f timeout: %f' % (ts, CLIENT_INPUT_TIMEOUT))
            self.__timedOut()
            return
        self.timer.startCallback(CLIENT_INPUT_TIMEOUT - ts, self.__timedOut)
        timeTask = Task.loop(Task(self.__countdown), Task.pause(0.2))
        taskMgr.add(timeTask, self.timerCountdownTaskName)

    def __stopTimer(self):
        self.notify.debug('__stopTimer()')
        self.timer.stop()
        taskMgr.remove(self.timerCountdownTaskName)

    def __countdown(self, task):
        if hasattr(self.townBattle, 'timer'):
            self.townBattle.updateTimer(int(self.timer.getT()))
        else:
            self.notify.warning('__countdown has tried to update a timer that has been deleted. Stopping timer')
            self.__stopTimer()
        return Task.done

    def enterWaitForInput(self, ts = 0):
        prop = self.getInteractiveProp()

        if prop:
            prop.gotoBattleCheer()
        self.choseAttackAlready = 0
        if self.localToonActive():
            self.__enterLocalToonWaitForInput()
            self.startTimer(ts)
        if self.needAdjustTownBattle == 1:
            self.__adjustTownBattle()
        return None

    def exitWaitForInput(self):
        self.notify.debug('exitWaitForInput()')
        if self.localToonActive():
            self.townBattle.setState('Off')
            base.camLens.setMinFov(self.camFov/(4./3.))
            self.ignore(self.localToonBattleEvent)
            self.__stopTimer()
        return None

    def __handleLocalToonBattleEvent(self, response):
        mode = response['mode']
        noAttack = 0
        if mode == 'Attack':
            self.notify.debug('got an attack')
            track = response['track']
            level = response['level']
            target = response['target']
            targetId = target
            if track == HEAL and not levelAffectsGroup(HEAL, level):
                if target >= 0 and target < len(self.activeToons):
                    targetId = self.activeToons[target].doId
                else:
                    self.notify.warning('invalid toon target: %d' % target)
                    track = -1
                    level = -1
                    targetId = -1
            elif track == HEAL and len(self.activeToons) == 1:
                self.notify.warning('invalid group target for heal')
                track = -1
                level = -1
            elif not attackAffectsGroup(track, level):
                if target >= 0 and target < len(self.activeSuits):
                    targetId = self.activeSuits[target].doId
                else:
                    target = -1
            if len(self.luredSuits) > 0:
                if track == TRAP or track == LURE and not levelAffectsGroup(LURE, level):
                    if target != -1:
                        suit = self.findSuit(targetId)
                        if self.luredSuits.count(suit) != 0:
                            self.notify.warning('Suit: %d was lured!' % targetId)
                            track = -1
                            level = -1
                            targetId = -1
                elif track == LURE:
                    if levelAffectsGroup(LURE, level) and len(self.activeSuits) == len(self.luredSuits):
                        self.notify.warning('All suits are lured!')
                        track = -1
                        level = -1
                        targetId = -1
            if track == TRAP:
                if target != -1:
                    if attackAffectsGroup(track, level):
                        pass
                    else:
                        suit = self.findSuit(targetId)
                        if suit.battleTrap != NO_TRAP:
                            self.notify.warning('Suit: %d was already trapped!' % targetId)
                            track = -1
                            level = -1
                            targetId = -1
            self.d_requestAttack(base.localAvatar.doId, track, level, targetId)
        elif mode == 'Run':
            self.notify.debug('got a run')
            self.d_toonRequestRun(base.localAvatar.doId)
        elif mode == 'SOS':
            targetId = response['id']
            self.notify.debug('got an SOS for friend: %d' % targetId)
            self.d_requestAttack(base.localAvatar.doId, SOS, -1, targetId)
        elif mode == 'NPCSOS':
            targetId = response['id']
            self.notify.debug('got an NPCSOS for friend: %d' % targetId)
            self.d_requestAttack(base.localAvatar.doId, NPCSOS, -1, targetId)
        elif mode == 'PETSOS':
            targetId = response['id']
            trickId = response['trickId']
            self.notify.debug('got an PETSOS for pet: %d' % targetId)
            self.d_requestAttack(base.localAvatar.doId, PETSOS, trickId, targetId)
        elif mode == 'PETSOSINFO':
            petProxyId = response['id']
            self.notify.debug('got a PETSOSINFO for pet: %d' % petProxyId)
            if petProxyId in base.cr.doId2do:
                self.notify.debug('pet: %d was already in the repository' % petProxyId)
                proxyGenerateMessage = 'petProxy-%d-generated' % petProxyId
                messenger.send(proxyGenerateMessage)
            else:
                self.d_requestPetProxy(base.localAvatar.doId, petProxyId)
            noAttack = 1
        elif mode == 'Pass':
            targetId = response['id']
            self.notify.debug('got a Pass')
            self.d_requestAttack(base.localAvatar.doId, PASS, -1, -1)
        elif mode == 'UnAttack':
            self.d_requestAttack(base.localAvatar.doId, UN_ATTACK, -1, -1)
            noAttack = 1
        elif mode == 'Fire':
            target = response['target']
            targetId = self.activeSuits[target].doId
            self.d_requestAttack(base.localAvatar.doId, FIRE, -1, targetId)
        else:
            self.notify.warning('unknown battle response')
            return
        if noAttack == 1:
            self.choseAttackAlready = 0
        else:
            self.choseAttackAlready = 1

    def __timedOut(self):
        if self.choseAttackAlready == 1:
            return
        self.notify.debug('WaitForInput timed out')
        if self.localToonActive():
            self.notify.debug('battle timed out')
            self.d_timeout(base.localAvatar.doId)

    def enterMakeMovie(self, ts = 0):
        self.notify.debug('enterMakeMovie()')
        return None

    def exitMakeMovie(self):
        return None

    def enterPlayMovie(self, ts):
        self.notify.debug('enterPlayMovie()')
        self.delayDeleteMembers()
        if self.hasLocalToon():
            NametagGlobals.setMasterArrowsOn(0)
        if ToontownBattleGlobals.SkipMovie:
            self.movie.play(ts, self.__handleMovieDone)
            self.movie.finish()
        else:
            self.movie.play(ts, self.__handleMovieDone)
        return None

    def __handleMovieDone(self):
        self.notify.debug('__handleMovieDone()')
        if self.hasLocalToon():
            self.d_movieDone(base.localAvatar.doId)
        self.movie.reset()

    def exitPlayMovie(self):
        self.notify.debug('exitPlayMovie()')
        self.movie.reset(finish=1)
        self._removeMembersKeep()
        self.townBattleAttacks = ([-1,
          -1,
          -1,
          -1],
         [-1,
          -1,
          -1,
          -1],
         [-1,
          -1,
          -1,
          -1],
         [0,
          0,
          0,
          0])
        return None

    def hasLocalToon(self):
        return self.toons.count(base.localAvatar) > 0

    def localToonPendingOrActive(self):
        return self.pendingToons.count(base.localAvatar) > 0 or self.activeToons.count(base.localAvatar) > 0

    def localToonActive(self):
        return self.activeToons.count(base.localAvatar) > 0

    def localToonActiveOrRunning(self):
        return self.activeToons.count(base.localAvatar) > 0 or self.runningToons.count(base.localAvatar) > 0

    def localToonRunning(self):
        return self.runningToons.count(base.localAvatar) > 0

    def enterHasLocalToon(self):
        self.notify.debug('enterHasLocalToon()')
        if base.cr.playGame.getPlace() != None:
            base.cr.playGame.getPlace().setState('battle', self.localToonBattleEvent)
            if localAvatar and hasattr(localAvatar, 'inventory') and localAvatar.inventory:
                prop = self.getInteractiveProp()

                if prop:
                    localAvatar.inventory.setInteractivePropTrackBonus(prop.BattleTrack)
        camera.wrtReparentTo(self)
        base.camLens.setMinFov(self.camFov/(4./3.))
        return

    def exitHasLocalToon(self):
        self.ignore(self.localToonBattleEvent)
        self.__stopTimer()
        if localAvatar and hasattr(localAvatar, 'inventory') and localAvatar.inventory:
            localAvatar.inventory.setInteractivePropTrackBonus(-1)
        stateName = None
        place = base.cr.playGame.getPlace()
        if place:
            stateName = place.fsm.getCurrentState().getName()
        if stateName == 'died':
            self.movie.reset()
            camera.reparentTo(render)
            camera.setPosHpr(localAvatar, 5.2, 5.45, localAvatar.getHeight() * 0.66, 131.5, 3.6, 0)
        else:
            camera.wrtReparentTo(base.localAvatar)
            messenger.send('localToonLeftBattle')
        base.camLens.setMinFov(settings['fov']/(4./3.))
        return

    def enterNoLocalToon(self):
        self.notify.debug('enterNoLocalToon()')
        return None

    def exitNoLocalToon(self):
        return None

    def setSkippingRewardMovie(self):
        self._skippingRewardMovie = True

    def enterWaitForServer(self):
        self.notify.debug('enterWaitForServer()')
        return None

    def exitWaitForServer(self):
        return None

    def createAdjustInterval(self, av, destPos, destHpr, toon = 0, run = 0):
        if run == 1:
            adjustTime = self.calcToonMoveTime(destPos, av.getPos(self))
        else:
            adjustTime = self.calcSuitMoveTime(destPos, av.getPos(self))
        self.notify.debug('creating adjust interval for: %d' % av.doId)
        adjustTrack = Sequence()
        if run == 1:
            adjustTrack.append(Func(av.loop, 'run'))
        else:
            adjustTrack.append(Func(av.loop, 'walk'))
        adjustTrack.append(Func(av.headsUp, self, destPos))
        adjustTrack.append(LerpPosInterval(av, adjustTime, destPos, other=self))
        adjustTrack.append(Func(av.setHpr, self, destHpr))
        adjustTrack.append(Func(av.loop, 'neutral'))
        return adjustTrack

    def __adjust(self, ts, callback):
        self.notify.debug('__adjust(%f)' % ts)
        adjustTrack = Parallel()
        if len(self.pendingSuits) > 0 or self.suitGone == 1:
            self.suitGone = 0
            numSuits = len(self.pendingSuits) + len(self.activeSuits) - 1
            index = 0
            for suit in self.activeSuits:
                point = self.suitPoints[numSuits][index]
                pos = suit.getPos(self)
                destPos = point[0]
                if self.isSuitLured(suit) == 1:
                    destPos = Point3(destPos[0], destPos[1] - MovieUtil.SUIT_LURE_DISTANCE, destPos[2])
                if pos != destPos:
                    destHpr = VBase3(point[1], 0.0, 0.0)
                    adjustTrack.append(self.createAdjustInterval(suit, destPos, destHpr))
                index += 1

            for suit in self.pendingSuits:
                point = self.suitPoints[numSuits][index]
                destPos = point[0]
                destHpr = VBase3(point[1], 0.0, 0.0)
                adjustTrack.append(self.createAdjustInterval(suit, destPos, destHpr))
                index += 1

        if len(self.pendingToons) > 0 or self.toonGone == 1:
            self.toonGone = 0
            numToons = len(self.pendingToons) + len(self.activeToons) - 1
            index = 0
            for toon in self.activeToons:
                point = self.toonPoints[numToons][index]
                pos = toon.getPos(self)
                destPos = point[0]
                if pos != destPos:
                    destHpr = VBase3(point[1], 0.0, 0.0)
                    adjustTrack.append(self.createAdjustInterval(toon, destPos, destHpr))
                index += 1

            for toon in self.pendingToons:
                point = self.toonPoints[numToons][index]
                destPos = point[0]
                destHpr = VBase3(point[1], 0.0, 0.0)
                adjustTrack.append(self.createAdjustInterval(toon, destPos, destHpr))
                index += 1

        if len(adjustTrack) > 0:
            self.notify.debug('creating adjust multitrack')
            e = Func(self.__handleAdjustDone)
            track = Sequence(adjustTrack, e, name=self.adjustName)
            self.storeInterval(track, self.adjustName)
            track.start(ts)
            if ToontownBattleGlobals.SkipMovie:
                track.finish()
        else:
            self.notify.warning('adjust() - nobody needed adjusting')
            self.__adjustDone()

    def __handleAdjustDone(self):
        self.notify.debug('__handleAdjustDone() - client adjust finished')
        self.clearInterval(self.adjustName)
        self.__adjustDone()

    def __stopAdjusting(self):
        self.notify.debug('__stopAdjusting()')
        self.clearInterval(self.adjustName)
        if self.adjustFsm.getCurrentState().getName() == 'Adjusting':
            self.adjustFsm.request('NotAdjusting')

    def __requestAdjustTownBattle(self):
        self.notify.debug('__requestAdjustTownBattle() curstate = %s' % self.fsm.getCurrentState().getName())
        if self.fsm.getCurrentState().getName() == 'WaitForInput':
            self.__adjustTownBattle()
        else:
            self.needAdjustTownBattle = 1

    def __adjustTownBattle(self):
        self.notify.debug('__adjustTownBattle()')
        if self.localToonActive() and len(self.activeSuits) > 0:
            self.notify.debug('__adjustTownBattle() - adjusting town battle')
            luredSuits = []
            for suit in self.luredSuits:
                if suit not in self.activeSuits:
                    self.notify.error('lured suit not in self.activeSuits')
                luredSuits.append(self.activeSuits.index(suit))

            trappedSuits = []
            for suit in self.activeSuits:
                if suit.battleTrap != NO_TRAP:
                    trappedSuits.append(self.activeSuits.index(suit))

            self.townBattle.adjustCogsAndToons(self.activeSuits, luredSuits, trappedSuits, self.activeToons)
            if hasattr(self, 'townBattleAttacks'):
                self.townBattle.updateChosenAttacks(self.townBattleAttacks[0], self.townBattleAttacks[1], self.townBattleAttacks[2], self.townBattleAttacks[3])
        self.needAdjustTownBattle = 0

    def __adjustDone(self):
        self.notify.debug('__adjustDone()')
        if self.hasLocalToon():
            self.d_adjustDone(base.localAvatar.doId)
        self.adjustFsm.request('NotAdjusting')

    def enterAdjusting(self, ts):
        self.notify.debug('enterAdjusting()')
        if self.localToonActive():
            self.__stopTimer()
        self.delayDeleteMembers()
        self.__adjust(ts, self.__handleAdjustDone)
        return None

    def exitAdjusting(self):
        self.notify.debug('exitAdjusting()')
        self.finishInterval(self.adjustName)
        self._removeMembersKeep()
        currStateName = self.fsm.getCurrentState().getName()
        if currStateName == 'WaitForInput' and self.localToonActive():
            self.startTimer()
        return None

    def enterNotAdjusting(self):
        self.notify.debug('enterNotAdjusting()')
        return None

    def exitNotAdjusting(self):
        return None

    def visualize(self):
        try:
            self.isVisualized
        except:
            self.isVisualized = 0

        if self.isVisualized:
            self.vis.removeNode()
            del self.vis
            self.detachNode()
            self.isVisualized = 0
        else:
            lsegs = LineSegs()
            lsegs.setColor(0.5, 0.5, 1, 1)
            lsegs.moveTo(0, 0, 0)
            for p in BattleBase.allPoints:
                lsegs.drawTo(p[0], p[1], p[2])

            p = BattleBase.allPoints[0]
            lsegs.drawTo(p[0], p[1], p[2])
            self.vis = self.attachNewNode(lsegs.create())
            self.reparentTo(render)
            self.isVisualized = 1

    def setupCollisions(self, name):
        self.lockout = CollisionTube(0, 0, 0, 0, 0, 9, 9)
        lockoutNode = CollisionNode(name)
        lockoutNode.addSolid(self.lockout)
        lockoutNode.setCollideMask(ToontownGlobals.WallBitmask)
        self.lockoutNodePath = self.attachNewNode(lockoutNode)
        self.lockoutNodePath.detachNode()

    def removeCollisionData(self):
        del self.lockout
        self.lockoutNodePath.removeNode()
        del self.lockoutNodePath

    def enableCollision(self):
        self.lockoutNodePath.reparentTo(self)
        if len(self.toons) < 4:
            self.accept(self.getCollisionName(), self.__handleLocalToonCollision)

    def __handleLocalToonCollision(self, collEntry):
        self.notify.debug('localToonCollision')
        if self.fsm.getCurrentState().getName() == 'Off':
            self.notify.debug('ignoring collision in Off state')
            return
        if not base.localAvatar.wantBattles:
            return
        if self._skippingRewardMovie:
            return
        base.cr.playGame.getPlace().setState('WaitForBattle')
        toon = base.localAvatar
        self.d_toonRequestJoin(toon.doId, toon.getPos(self))
        base.localAvatar.preBattleHpr = base.localAvatar.getHpr(render)
        self.localToonFsm.request('WaitForServer')
        self.onWaitingForJoin()

    def onWaitingForJoin(self):
        pass

    def denyLocalToonJoin(self):
        self.notify.debug('denyLocalToonJoin()')
        place = self.cr.playGame.getPlace()
        if place.fsm.getCurrentState().getName() == 'WaitForBattle':
            place.setState('walk')
        self.localToonFsm.request('NoLocalToon')

    def disableCollision(self):
        self.ignore(self.getCollisionName())
        self.lockoutNodePath.detachNode()

    def openBattleCollision(self):
        if not self.hasLocalToon():
            self.enableCollision()

    def closeBattleCollision(self):
        self.ignore(self.getCollisionName())

    def getCollisionName(self):
        return 'enter' + self.lockoutNodePath.getName()

    def setFireCount(self, amount):
        self.fireCount = amount

    def getFireCount(self):
        return self.fireCount