from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from toontown.toonbase import ToontownGlobals
import HouseGlobals
import time, random

from toontown.fishing.DistributedFishingPondAI import DistributedFishingPondAI
from toontown.fishing import FishingTargetGlobals, FishGlobals
from toontown.safezone import TreasureGlobals
from toontown.safezone.SZTreasurePlannerAI import SZTreasurePlannerAI
from toontown.safezone import DistributedEFlyingTreasureAI
from toontown.safezone import ButterflyGlobals
from toontown.safezone import DistributedButterflyAI
from toontown.safezone.DistributedFishingSpotAI import DistributedFishingSpotAI
from toontown.parties.DistributedPartyJukeboxActivityAI import DistributedPartyJukeboxActivityAI

from DistributedGardenBoxAI import *
from DistributedGardenPlotAI import *
from DistributedGagTreeAI import *
from DistributedFlowerAI import *
from DistributedStatuaryAI import *
from DistributedToonStatuaryAI import *
from DistributedAnimatedStatuaryAI import *
from DistributedChangingStatuaryAI import *
import GardenGlobals

from DistributedCannonAI import *
from DistributedTargetAI import *
import CannonGlobals

# planted, waterLevel, lastCheck, growthLevel, optional
NULL_PLANT = [-1, -1, 0, 0, 0]
NULL_TREES = [NULL_PLANT] * 8
NULL_FLOWERS = [NULL_PLANT] * 10
NULL_STATUARY = 0

NULL_DATA = {'trees': NULL_TREES, 'statuary': NULL_STATUARY, 'flowers': NULL_FLOWERS}

from direct.distributed.PyDatagramIterator import *
from direct.distributed.PyDatagram import *
    
class Garden:
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedEstateAI')
    
    WANT_FLOWERS = True # puke
    WANT_TREES = True
    WANT_STATUARY = True
    
    def __init__(self, air, avId):
        self.air = air
        self.avId = avId
        
        self.trees = set()
        self.flowers = set()
        self.objects = set()
        
        if not self.air.dbConn:
            self.notify.warning('Not using mongodb, garden data will be non-persistent')
            self.data = NULL_DATA.copy()
            
        else:
            d = self.air.dbGlobalCursor.gardens.find_one({'avId': avId})
            if d is None:
                self.data = NULL_DATA.copy()
                self.air.dbGlobalCursor.gardens.update({'avId': avId}, {'$set': NULL_DATA}, upsert=True)
                
            else:
                self.data = d
                
        self.data.pop('_id', None)
        
    def destroy(self):
        messenger.send('garden-%d-666-going-down' % self.avId)
        
        for tree in self.trees:
            tree.requestDelete()
            
        for flower in self.flowers:
            flower.requestDelete()
            
        for object in self.objects:
            object.requestDelete()
            
        self.air = None
        self.estateMgr = None
        
    def create(self, estateMgr):
        self.estateMgr = estateMgr
        
        if self.avId not in estateMgr.toons:
            estateMgr.notify.warning('Garden associated to unknown avatar %d, deleting...' % self.avId)
            return False
        
        houseIndex = estateMgr.toons.index(self.avId)

        if self.WANT_FLOWERS:
            boxIndex = 0
            boxes = []
            boxDefs = GardenGlobals.estateBoxes[houseIndex]
            for x, y, h, boxType in boxDefs:
                box = DistributedGardenBoxAI(self)
            
                box.setTypeIndex(boxType)
                box.setPos(x, y, 0)
                box.setH(h)
                box.setOwnerIndex(houseIndex)
                box.generateWithRequired(estateMgr.zoneId)
            
                self.objects.add(box)
                boxes.append(box)
                boxIndex += 1
                
            self._boxes = boxes
                
        plots = GardenGlobals.estatePlots[houseIndex]
        treeIndex = 0
        flowerIndex = 0
        for plot, (x, y, h, type) in enumerate(plots):
            if type == GardenGlobals.GAG_TREE_TYPE and self.WANT_TREES:
                data = self.data['trees'][treeIndex]
                
                planted, waterLevel, lastCheck, growthLevel, lastHarvested = data
                
                if planted != -1:
                    obj = self.plantTree(treeIndex, planted, waterLevel=waterLevel,
                                         lastCheck=lastCheck, growthLevel=growthLevel,
                                         lastHarvested=lastHarvested, generate=False)
                    
                    self.trees.add(obj)
                    
                else:
                    obj = self.placePlot(treeIndex)
                    
                obj.setPos(x, y, 0)
                obj.setH(h)
                obj.setPlot(plot)
                obj.setOwnerIndex(houseIndex)
                obj.generateWithRequired(estateMgr.zoneId)
                treeIndex += 1
                
            elif type == GardenGlobals.FLOWER_TYPE and self.WANT_FLOWERS:
                data = self.data['flowers'][flowerIndex]
                
                planted, waterLevel, lastCheck, growthLevel, variety = data
                
                if planted != -1:
                    obj = self.plantFlower(flowerIndex, planted, variety, waterLevel=waterLevel,
                                           lastCheck=lastCheck, growthLevel=growthLevel,
                                           generate=False)
                   
                else:
                    obj = self.placePlot(flowerIndex)
                    obj.flowerIndex = flowerIndex
           
                obj.setPlot(plot)
                obj.setOwnerIndex(houseIndex)
                obj.generateWithRequired(estateMgr.zoneId)
                
                index = (0, 1, 2, 2, 2, 3, 3, 3, 4, 4)[flowerIndex]
                idx = (0, 0, 0, 1, 2, 0, 1, 2, 0, 1)[flowerIndex]
                obj.sendUpdate('setBoxDoId', [boxes[index].doId, idx])
                flowerIndex += 1
                
            elif type == GardenGlobals.STATUARY_TYPE and self.WANT_STATUARY:
                data = self.data['statuary']
                if data == 0:
                    obj = self.placePlot(-1)
                    
                else:
                    obj = self.placeStatuary(data, generate=False)
                 
                obj.setPos(x, y, 0)
                obj.setH(h)
                obj.setPlot(plot)
                obj.setOwnerIndex(houseIndex)
                obj.generateWithRequired(estateMgr.zoneId)

        for tree in self.trees:
            tree.calcDependencies()
            
        self.reconsiderAvatarOrganicBonus()
                
        return True
        
    def hasTree(self, track, index):
        x = track * 7 + index
        for tree in self.data['trees']:
            if tree[0] == x:
                return True
                
        return False
        
    def getTree(self, track, index):
        for tree in self.trees:
            if tree.typeIndex == track * 7 + index:
                return tree
        
    def plantTree(self, treeIndex, value, plot=None, waterLevel=-1,
                  lastCheck=0, growthLevel=0, lastHarvested=0,
                  ownerIndex=-1, plotId=-1, pos=None, generate=True):
        if not self.air:
            return           
                
        if plot:
            if plot not in self.objects:
                return
                
            plot.requestDelete()
            self.objects.remove(plot)
            
        tree = DistributedGagTreeAI(self)
                    
        tree.setTypeIndex(value)
        tree.setWaterLevel(waterLevel)
        tree.setGrowthLevel(growthLevel)
        if ownerIndex != -1:
            tree.setOwnerIndex(ownerIndex)
            
        if plotId != -1:
            tree.setPlot(plotId)
            
        if pos is not None:
            pos, h = pos
            tree.setPos(pos)
            tree.setH(h)
            
        tree.treeIndex = treeIndex
        tree.calculate(lastHarvested, lastCheck)
        self.trees.add(tree)
        
        if generate:
            tree.generateWithRequired(self.estateMgr.zoneId)
            
        return tree
        
    def placePlot(self, treeIndex):
        obj = DistributedGardenPlotAI(self)
        obj.treeIndex = treeIndex
        self.objects.add(obj)
        
        return obj
     
    def plantFlower(self, flowerIndex, species, variety, plot=None, waterLevel=-1,
                    lastCheck=0, growthLevel=0, ownerIndex=-1, plotId=-1, generate=True):
        if not self.air:
            return           
                
        if plot:
            if plot not in self.objects:
                return
                
            plot.requestDelete()
            self.objects.remove(plot)
            
        flower = DistributedFlowerAI(self)
        
        flower.setTypeIndex(species)
        flower.setVariety(variety)
        flower.setWaterLevel(waterLevel)
        flower.setGrowthLevel(growthLevel)
        if ownerIndex != -1:
            flower.setOwnerIndex(ownerIndex)
            
        if plotId != -1:
            flower.setPlot(plotId)
            
        flower.flowerIndex = flowerIndex
        flower.calculate(lastCheck)
        self.flowers.add(flower)
        
        if generate:
            flower.generateWithRequired(self.estateMgr.zoneId)
            
        return flower
        
    def placeStatuary(self, data, plot=None, plotId=-1, ownerIndex=-1,
                      pos=None, generate=True):
        if not self.air:
            return
            
        if plot:
            if plot not in self.objects:
                return
                
            plot.requestDelete()
            self.objects.remove(plot)
            
        data, lastCheck, index, growthLevel = self.S_unpack(data)
                    
        dclass = DistributedStatuaryAI
        if index in GardenGlobals.ToonStatuaryTypeIndices:
            dclass = DistributedToonStatuaryAI
            
        elif index in GardenGlobals.ChangingStatuaryTypeIndices:
            dclass = DistributedChangingStatuaryAI
            
        elif index in GardenGlobals.AnimatedStatuaryTypeIndices:
            dclass = DistributedAnimatedStatuaryAI
            
        obj = dclass(self)
        obj.growthLevel = growthLevel
        obj.index = index
        obj.data = data
        
        if ownerIndex != -1:
            obj.setOwnerIndex(ownerIndex)
            
        if plotId != -1:
            obj.setPlot(plotId)
            
        if pos is not None:
            pos, h = pos
            obj.setPos(pos)
            obj.setH(h)
        
        obj.calculate(lastCheck)
        
        self.objects.add(obj)
        
        if generate:
            obj.announceGenerate()
            
        return obj
            
    # Data structure
    # VERY HIGH (vh) (64-bit)
    #    high high (H) = data (32-bit)
    #    high low (L) = lastCheck (32-bit)
    # VERY LOW (vl) (16-bit)
    #    low high (h) = index (8-bit)
    #    low low (l) = growthLevel (8-bit)
     
    @staticmethod
    def S_pack(data, lastCheck, index, growthLevel):
        vh = data << 32 | lastCheck
        vl = index << 8 | growthLevel
    
        return vh << 16 | vl
    
    @staticmethod
    def S_unpack(x):
        vh = x >> 16
        vl = x & 0xFFFF
    
        data = vh >> 32
        lastCheck = vh & 0xFFFFFFFF
    
        index = vl >> 8
        growthLevel = vl & 0xFF
    
        return data, lastCheck, index, growthLevel
        
    def getNullPlant(self):
        return NULL_PLANT
        
    def reconsiderAvatarOrganicBonus(self):
        av = self.air.doId2do.get(self.avId)
        if not av:
            return
            
        bonus = [-1] * 7
        for track in xrange(7):
            for level in xrange(8):#7
                if not self.hasTree(track, level):
                    break
                    
                tree = self.getTree(track, level)
                if tree.getGrowthLevel() < tree.growthThresholds[1] or tree.getWilted():
                    break
                    
            bonus[track] = level - 1
            
        av.b_setTrackBonusLevel(bonus)

    def update(self):
        if self.air.dbConn:
            self.air.dbGlobalCursor.gardens.update({'avId': self.avId}, {'$set': self.data}, upsert=True)

class GardenManager:
    def __init__(self, mgr):
        self.mgr = mgr
        self.gardens = {}
                    
    def handleSingleGarden(self, avId):
        g = Garden(self.mgr.air, avId)
        g.gardenMgr = self
        res = g.create(self.mgr)
        if res:
            self.gardens[avId] = g
        
    def destroy(self):
        for garden in self.gardens.values():
            garden.destroy()
            
        del self.gardens

class Rental:
    def __init__(self, estate):
        self.estate = estate
        self.objects = set()
        
    def destroy(self):
        del self.estate
        for object in self.objects:
            if not object.isDeleted():
                object.requestDelete()
                taskMgr.remove(object.uniqueName('delete'))
        self.objects = set()
        
class CannonRental(Rental):
    def generateObjects(self):
        target = DistributedTargetAI(self.estate.air)
        target.generateWithRequired(self.estate.zoneId)
        
        for drop in CannonGlobals.cannonDrops:
            cannon = DistributedCannonAI(self.estate.air, self.estate.zoneId, target.doId, *drop)
            cannon.generateWithRequired(self.estate.zoneId)
            self.objects.add(cannon)
            
        self.generateTreasures()
        self.estate.b_setClouds(1)
        
    def destroy(self):
        self.estate.b_setClouds(0)
        Rental.destroy(self)
            
    def generateTreasures(self):
        doIds = []
        z = 35
        
        for i in xrange(20):
            x = random.randint(100, 300) - 200
            y = random.randint(100, 300) - 200
            treasure = DistributedEFlyingTreasureAI.DistributedEFlyingTreasureAI(self.estate.air, self, 7, x, y, z)
            treasure.generateWithRequired(self.estate.zoneId)
            self.objects.add(treasure)
            doIds.append(treasure.doId)
            
        self.estate.sendUpdate('setTreasureIds', [doIds])
        
    def grabAttempt(self, avId, treasureId):
        av = self.estate.air.doId2do.get(avId)
        if av == None:
            self.estate.air.writeServerEvent('suspicious', avId, 'TreasurePlannerAI.grabAttempt unknown avatar')
            self.estate.notify.warning('avid: %s does not exist' % avId)
            return
            
        treasure = self.estate.air.doId2do.get(treasureId)
        if self.validAvatar(av):
            treasure.d_setGrab(avId)
            self.deleteTreasureSoon(treasure)
            
        else:
            treasure.d_setReject()

    def deleteTreasureSoon(self, treasure):
        taskName = treasure.uniqueName('delete')
        taskMgr.doMethodLater(5, self.__deleteTreasureNow, taskName, extraArgs=(treasure, taskName))

    def __deleteTreasureNow(self, treasure, taskName):
        treasure.requestDelete()
        
    def validAvatar(self, av):
        if av.getMaxHp() == av.getHp():
            return 0
            
        av.toonUp(3)
        return 1

class DistributedEstateAI(DistributedObjectAI):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedEstateAI')
    
    def __init__(self, air):
        DistributedObjectAI.__init__(self, air)
        self.toons = [0, 0, 0, 0, 0, 0]
        self.items = [[], [], [], [], [], []]
        self.estateType = 0
        self.cloudType = 0
        self.dawnTime = 0
        self.lastEpochTimestamp = 0
        self.rentalTimestamp = 0
        self.houses = [None] * 6
        self.rentalType = 0
        self.rentalHandle = None
        
        self.pond = None
        self.jukebox = None
        self.spots = []
        self.butterflies = []

        self.owner = None
        
        self.gardenManager = GardenManager(self)
        self.__pendingGardens = {}
    
    @property
    def hostId(self):
        return 1000000001
        
    def generate(self):
        DistributedObjectAI.generate(self)
        
        self.pond = DistributedFishingPondAI(simbase.air)
        self.pond.setArea(ToontownGlobals.MyEstate)
        self.pond.generateWithRequired(self.zoneId)
        self.pond.start()

        treasureType, healAmount, spawnPoints, spawnRate, maxTreasures = TreasureGlobals.SafeZoneTreasureSpawns[ToontownGlobals.MyEstate]
        self.treasurePlanner = SZTreasurePlannerAI(self.zoneId, treasureType, healAmount, spawnPoints, spawnRate, maxTreasures)
        self.treasurePlanner.start()

        spot = DistributedFishingSpotAI(self.air)
        spot.setPondDoId(self.pond.getDoId())
        spot.setPosHpr(49.1029, -124.805, 0.344704, 90, 0, 0)
        spot.generateWithRequired(self.zoneId)
        self.spots.append(spot)

        spot = DistributedFishingSpotAI(self.air)
        spot.setPondDoId(self.pond.getDoId())
        spot.setPosHpr(46.5222, -134.739, 0.390713, 75, 0, 0)
        spot.generateWithRequired(self.zoneId)
        self.spots.append(spot)

        spot = DistributedFishingSpotAI(self.air)
        spot.setPondDoId(self.pond.getDoId())
        spot.setPosHpr(41.31, -144.559, 0.375978, 45, 0, 0)
        spot.generateWithRequired(self.zoneId)
        self.spots.append(spot)

        spot = DistributedFishingSpotAI(self.air)
        spot.setPondDoId(self.pond.getDoId())
        spot.setPosHpr(46.8254, -113.682, 0.46015, 135, 0, 0)
        spot.generateWithRequired(self.zoneId)
        self.spots.append(spot)
        
        self.jukebox = DistributedPartyJukeboxActivityAI(self.air, self.doId, (0, 0, 0, 0))
        self.jukebox.generateWithRequired(self.zoneId)
        self.jukebox.sendUpdate('setX', [-21.8630])
        self.jukebox.sendUpdate('setY', [-154.669])
        self.jukebox.sendUpdate('setH', [148.7050])
        self.jukebox.sendUpdate('unloadSign')

        ButterflyGlobals.generateIndexes(self.zoneId, ButterflyGlobals.ESTATE)
        for i in xrange(0, ButterflyGlobals.NUM_BUTTERFLY_AREAS[ButterflyGlobals.ESTATE]):
            for j in xrange(0, ButterflyGlobals.NUM_BUTTERFLIES[ButterflyGlobals.ESTATE]):
                butterfly = DistributedButterflyAI.DistributedButterflyAI(self.air, ButterflyGlobals.ESTATE, i, self.zoneId)
                butterfly.generateWithRequired(self.zoneId)
                butterfly.start()
                self.butterflies.append(butterfly)

    def destroy(self):
        for house in self.houses:
            if house:
                house.requestDelete()
        for butterfly in self.butterflies:
            if butterfly:
                butterfly.requestDelete()
        del self.houses[:]
        if self.pond:
            for spot in self.spots:
                spot.requestDelete()
            self.spots = []
            self.pond.requestDelete()
            self.pond = None
        if self.jukebox:
            self.jukebox.requestDelete()
        if self.treasurePlanner:
            self.treasurePlanner.stop()
                
        self.gardenManager.destroy()
        if self.rentalHandle:
            self.rentalHandle.destroy()
            self.rentalHandle = None
                
        self.requestDelete()

    def addDistObj(self, distObj):
        self.doId2do[distObj.doId] = distObj

    def setClientReady(self):
        self.sendUpdate('setEstateReady', [])

    def setEstateType(self, type):
        self.estateType = type
        
    def d_setEstateType(self, type):
        self.sendUpdate('setEstateType', [type])
        
    def b_setEstateType(self, type):
        self.setEstateType(type)
        self.d_setEstateType(type)

    def getEstateType(self):
        return self.estateType

    def requestServerTime(self):
        avId = self.air.getAvatarIdFromSender()
        self.sendUpdateToAvatarId(avId, 'setServerTime', [time.time() % HouseGlobals.DAY_NIGHT_PERIOD])

    def setDawnTime(self, dawnTime):
        self.dawnTime = dawnTime
        
    def d_setDawnTime(self, dawnTime):
        self.sendUpdate('setDawnTime', [dawnTime])
        
    def b_setDawnTime(self, dawnTime):
        self.setDawnTime(dawnTime)
        self.d_setDawnTime(dawnTime)
        
    def getDawnTime(self):
        return self.dawnTime

    def setLastEpochTimeStamp(self, last):
        self.lastEpochTimestamp = last
        
    def d_setLastEpochTimeStamp(self, last):
        self.sendUpdate('setLastEpochTimeStamp', [last])
        
    def b_setLastEpochTimeStamp(self, last):
        self.setLastEpochTimeStamp(last)
        self.d_setLastEpochTimeStamp(last)
        
    def getLastEpochTimeStamp(self):
        return self.lastEpochTimestamp

    def setRentalTimeStamp(self, rental):
        self.rentalTimestamp = rental
        
    def d_setRentalTimeStamp(self, rental):
        self.sendUpdate('setRentalTimeStamp', [rental])
        
    def b_setRentalTimeStamp(self, rental):
        self.setRentalTimeStamp(rental)
        self.d_setRentalTimeStamp(rental)
        
    def getRentalTimeStamp(self):
        return self.rentalTimestamp

    def b_setRentalType(self, type):
        self.d_setRentalType(type)
        self.setRentalType(type)
        
    def d_setRentalType(self, type):
        self.sendUpdate('setRentalType', [type])
        
    def setRentalType(self, type):
        expirestamp = self.getRentalTimeStamp()
        if expirestamp == 0:
            expire = 0
            
        else:
            expire = int(expirestamp - time.time())
            
        if expire < 0:
            self.rentalType = 0
            self.d_setRentalType(0)
            self.b_setRentalTimeStamp(0)
        
        else:
            if self.rentalType == type:
                return
                
            self.rentalType = type
            if self.rentalHandle:
                self.rentalHandle.destroy()
                self.rentalHandle = None
                
            if self.rentalType == ToontownGlobals.RentalCannon:
                self.rentalHandle = CannonRental(self)
                
            else:
                self.notify.warning('Unknown rental %s' % self.rentalType)
                return
                
            self.rentalHandle.generateObjects()
        
    def getRentalType(self):
        return self.rentalType
        
    def rentItem(self, rentType, duration):
        self.b_setRentalTimeStamp(time.time() + duration * 60)
        self.b_setRentalType(rentType)
        
    def setSlot0ToonId(self, id):
        self.toons[0] = id
        
    def d_setSlot0ToonId(self, id):
        self.sendUpdate('setSlot0ToonId', [id])
        
    def b_setSlot0ToonId(self, id):
        self.setSlot0ToonId(id)
        self.d_setSlot0ToonId(id)
        
    def getSlot0ToonId(self):
        return self.toons[0]

    def setSlot0Items(self, items):
        self.items[0] = items

    def d_setSlot0Items(self, items):
        self.sendUpdate('setSlot5Items', [items])
        
    def b_setSlot0Items(self, items):
        self.setSlot0Items(items)
        self.d_setSlot0Items(items)
        
    def getSlot0Items(self):
        return self.items[0]
        
    def setSlot1ToonId(self, id):
        self.toons[1] = id

    def d_setSlot1ToonId(self, id):
        self.sendUpdate('setSlot1ToonId', [id])
        
    def b_setSlot1ToonId(self, id):
        self.setSlot1ToonId(id)
        self.d_setSlot1ToonId(id)
        
    def getSlot1ToonId(self):
        return self.toons[1]
        
    def setSlot1Items(self, items):
        self.items[1] = items
        
    def d_setSlot1Items(self, items):
        self.sendUpdate('setSlot2Items', [items])
        
    def b_setSlot1Items(self, items):
        self.setSlot2Items(items)
        self.d_setSlot2Items(items)
        
    def getSlot1Items(self):
        return self.items[1]

    def setSlot2ToonId(self, id):
        self.toons[2] = id

    def d_setSlot2ToonId(self, id):
        self.sendUpdate('setSlot2ToonId', [id])
        
    def b_setSlot2ToonId(self, id):
        self.setSlot2ToonId(id)
        self.d_setSlot2ToonId(id)
        
    def getSlot2ToonId(self):
        return self.toons[2]

    def setSlot2Items(self, items):
        self.items[2] = items

    def d_setSlot2Items(self, items):
        self.sendUpdate('setSlot2Items', [items])
        
    def b_setSlot2Items(self, items):
        self.setSlot2Items(items)
        self.d_setSlot2Items(items)
        
    def getSlot2Items(self):
        return self.items[2]

    def setSlot3ToonId(self, id):
        self.toons[3] = id
        
    def d_setSlot3ToonId(self, id):
        self.sendUpdate('setSlot3ToonId', [id])
        
    def b_setSlot3ToonId(self, id):
        self.setSlot3ToonId(id)
        self.d_setSlot3ToonId(id)
        
    def getSlot3ToonId(self):
        return self.toons[3]

    def setSlot3Items(self, items):
        self.items[3] = items
        
    def d_setSlot3Items(self, items):
        self.sendUpdate('setSlot3Items', [items])
        
    def b_setSlot3Items(self, items):
        self.setSlot3Items(items)
        self.d_setSlot3Items(items)
        
    def getSlot3Items(self):
        return self.items[3]

    def setSlot4ToonId(self, id):
        self.toons[4] = id
        
    def d_setSlot4ToonId(self, id):
        self.sendUpdate('setSlot4ToonId', [id])
        
    def b_setSlot5ToonId(self, id):
        self.setSlot4ToonId(id)
        self.d_setSlot4ToonId(id)
        
    def getSlot4ToonId(self):
        return self.toons[4]

    def setSlot4Items(self, items):
        self.items[4] = items
        
    def d_setSlot4Items(self, items):
        self.sendUpdate('setSlot4Items', [items])
        
    def b_setSlot4Items(self, items):
        self.setSlot4Items(items)
        self.d_setSlot4Items(items)
        
    def getSlot4Items(self):
        return self.items[4]

    def setSlot5ToonId(self, id):
        self.toons[5] = id
        
    def d_setSlot5ToonId(self, id):
        self.sendUpdate('setSlot5ToonId', [id])
        
    def b_setSlot5ToonId(self, id):
        self.setSlot5ToonId(id)
        self.d_setSlot5ToonId(id)
        
    def getSlot5ToonId(self):
        return self.toons[5]

    def setSlot5Items(self, items):
        self.items[5] = items
        
    def d_setSlot5Items(self, items):
        self.sendUpdate('setSlot5Items', [items])
        
    def b_setSlot5Items(self, items):
        self.setSlot5Items(items)
        self.d_setSlot5Items(items)
        
    def getSlot5Items(self):
        return self.items[5]

    def setIdList(self, idList):
        for i in xrange(len(idList)):
            if i >= 6:
                return
            self.toons[i] = idList[i]
        
    def d_setIdList(self, idList):
        self.sendUpdate('setIdList', [idList])
    
    def b_setIdList(self, idList):
        self.setIdList(idList)
        self.d_setIdLst(idList)
        
    def completeFlowerSale(self, flag):
        if not flag:
            return
            
        avId = self.air.getAvatarIdFromSender()
        av = self.air.doId2do.get(avId)
        if not av:
            return
            
        collection = av.flowerCollection
        
        earning = 0
        newSpecies = 0
        for flower in av.flowerBasket.getFlower():
            if collection.collectFlower(flower) == GardenGlobals.COLLECT_NEW_ENTRY:
                newSpecies += 1
                
            earning += flower.getValue()
        
        av.b_setFlowerBasket([], [])
        av.d_setFlowerCollection(*av.flowerCollection.getNetLists())
        av.addMoney(earning)
        
        oldSpecies = len(collection) - newSpecies
        dt = abs(len(collection) // 10 - oldSpecies // 10)
        if dt:
            self.notify.info('%d is getting a gardening trophy!' % avId)
            
            maxHp = av.getMaxHp()
            maxHp = min(ToontownGlobals.MaxHpLimit, maxHp + dt)
            av.b_setMaxHp(maxHp)
            av.toonUp(maxHp)
            
            self.sendUpdate('awardedTrophy', [avId])
        
        av.b_setGardenTrophies(range(len(collection) // 10))
    
    def completeFishSale(self):
        avId = self.air.getAvatarIdFromSender()
        av = self.air.doId2do.get(avId)
        
        if not av:
            return

        if self.air.fishManager.creditFishTank(av):
            self.sendUpdateToAvatarId(avId, 'thankSeller', [ToontownGlobals.FISHSALE_TROPHY, len(av.fishCollection), FishGlobals.getTotalNumFish()])
        else:
            self.sendUpdateToAvatarId(avId, 'thankSeller', [ToontownGlobals.FISHSALE_COMPLETE, 0, 0])
                         
    def setClouds(self, clouds):
        self.cloudType = clouds
        
    def d_setClouds(self, clouds):
        self.sendUpdate('setClouds', [clouds])
        
    def b_setClouds(self, clouds):
        self.setClouds(clouds)
        self.d_setClouds(clouds)
        
    def getClouds(self):
        return self.cloudType
        
    # Garden methods
    def getToonSlot(self, avId):
        if avId not in self.toons:
            return
            
        return self.toons.index(avId)
     
    def setSlot0Garden(self, flag):
        self.__pendingGardens[0] = flag

    def setSlot1Garden(self, flag):
        self.__pendingGardens[1] = flag

    def setSlot2Garden(self, flag):
        self.__pendingGardens[2] = flag
       
    def setSlot3Garden(self, flag):
        self.__pendingGardens[3] = flag
   
    def setSlot4Garden(self, flag):
        self.__pendingGardens[4] = flag

    def setSlot5Garden(self, flag):
        self.__pendingGardens[5] = flag
    
    def placeStarterGarden(self, avId, record=1):
        av = self.air.doId2do.get(avId)
        if not av:
            return

        slot = self.getToonSlot(avId)
        if slot is None:
            return
            
        if record:
            av.b_setGardenStarted(1)
            self.sendUpdate('setSlot%dGarden' % slot, ['started'])
        
        self.notify.info('placeStarterGarden %d %d' % (avId, slot))
        self.gardenManager.handleSingleGarden(avId)
        
    def announceGenerate(self):
        DistributedObjectAI.announceGenerate(self)
        self.sendUpdate('setIdList', [self.toons])
        
        for index, started in self.__pendingGardens.items():
            if started:
                self.gardenManager.handleSingleGarden(self.toons[index])
            
        self.__pendingGardens = {}
        if config.GetBool('fake-garden-started-ai', False):
            self.placeStarterGarden(100000002, 0)