class PartyPlanner(DirectFrame, FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('PartyPlanner')

    def __init__(self, doneEvent = None):
        FSM.__init__(self, 'PartyPlannerFSM')
        DirectFrame.__init__(self)
        self.doneEvent = doneEvent
        self.stateArray = ['Off',
         'Welcome',
         'PartyEditor',
         'Guests',
         'Date',
         'Time',
         'Invitation',
         'Farewell']
        self.partyTime = base.cr.toontownTimeManager.getCurServerDateTime()
        self.partyNowTime = base.cr.toontownTimeManager.getCurServerDateTime()
        minutesToNextFifteen = 15 - self.partyTime.minute % 15
        self.cleanPartyTime = self.partyTime + timedelta(minutes=minutesToNextFifteen, seconds=-self.partyTime.second)
        self.partyTime = self.cleanPartyTime
        self.guests = []
        self.isPrivate = False
        self.selectedCalendarGuiDay = None
        self.gui = loader.loadModel('phase_4/models/parties/partyPlannerGUI')
        self.partyDuration = timedelta(hours=PartyGlobals.DefaultPartyDuration)
        self.timeTypeToMaxValue = {'hour': 23,
         'minute': 59}
        self.timeTypeToChangeAmount = {'hour': (1, -1),
         'minute': (15, -15),
         'ampm': (1, -1)}
        self.partyInfo = None
        self.asapMinuteRounding = base.config.GetInt('party-asap-minute-rounding', PartyGlobals.PartyPlannerAsapMinuteRounding)
        self.load()
        self.request('Welcome')
        return

    def enterWelcome(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.DISABLED
        self.prevButton.hide()
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.welcomePage.show()
        self.partyPlannerHead.reparentTo(self.welcomePage)
        self.partyPlannerHead.startBlink()
        self.partyPlannerHead.startLookAround()
        self.nametagNP.reparentTo(self.welcomePage)
        self.chatNP.reparentTo(self.welcomePage)

    def exitWelcome(self):
        self.welcomePage.hide()
        self.prevButton.show()
        self.partyPlannerHead.stopBlink()
        self.partyPlannerHead.stopLookAround()

    def enterPartyEditor(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton['state'] = DirectGuiGlobals.DISABLED
        self.nextButton.hide()
        self.partyEditorPage.show()
        self.okWithGroundsGui.doneStatus = ''
        self.partyEditor.request('Idle')

    def exitPartyEditor(self):
        self.partyEditor.request('Hidden')
        self.partyEditorPage.hide()

    def enterGuests(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.show()
        self.guestPage.show()

    def exitGuests(self):
        self.guests = []
        for friendCheckBox in self.friendList['items']:
            if friendCheckBox['indicatorValue']:
                self.guests.append(friendCheckBox.getPythonTag('id'))

        self.guestPage.hide()

    def enterDate(self, *args):
        self.prevButton.show()
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        if self.selectedCalendarGuiDay is None:
            self.nextButton['state'] = DirectGuiGlobals.DISABLED
            self.nextButton.hide()
            self.makePartyNowButton.show()
        self.datePage.show()
        return

    def exitDate(self):
        self.datePage.hide()
        self.nextButton.show()
        if self.selectedCalendarGuiDay is not None:
            self.partyTime = self.cleanPartyTime
            self.alterPartyTime(year=self.selectedCalendarGuiDay.myDate.year, month=self.selectedCalendarGuiDay.myDate.month, day=self.selectedCalendarGuiDay.myDate.day)
        else:
            self.partyNowTime = self.calcAsapTime()
            self.partyTime = self.partyNowTime
        return

    def calcAsapTime(self):
        curServerTime = base.cr.toontownTimeManager.getCurServerDateTime()
        baseTime = curServerTime
        baseTime = baseTime.replace(baseTime.year, baseTime.month, baseTime.day, baseTime.hour, baseTime.minute, second=0, microsecond=0)
        minute = curServerTime.minute
        remainder = minute % self.asapMinuteRounding
        if remainder:
            baseTime += timedelta(minutes=self.asapMinuteRounding - remainder)
        else:
            baseTime += timedelta(minutes=self.asapMinuteRounding)
        return baseTime

    def enterTime(self, *args):
        self.prevButton.show()
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.show()
        self.timePage.show()
        self.timePageRecapToontownTimeLabel2['text'] = '%s' % PartyUtils.formatDateTime(self.partyTime)
        self.timePageRecapLocalTimeLabel['text'] = '%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True))

    def exitTime(self):
        self.timePage.hide()
        self.nextButton.show()

    def enterInvitation(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.hide()
        defaultInviteTheme = PartyGlobals.InviteTheme.GenericMale
        if hasattr(base.cr, 'newsManager') and base.cr.newsManager:
            if ToontownGlobals.VICTORY_PARTY_HOLIDAY in base.cr.newsManager.getHolidayIdList():
                defaultInviteTheme = PartyGlobals.InviteTheme.VictoryParty
            elif ToontownGlobals.KARTING_TICKETS_HOLIDAY in base.cr.newsManager.getHolidayIdList() or ToontownGlobals.CIRCUIT_RACING_EVENT in base.cr.newsManager.getHolidayIdList():
                defaultInviteTheme = PartyGlobals.InviteTheme.Racing
            elif ToontownGlobals.VALENTINES_DAY in base.cr.newsManager.getHolidayIdList():
                defaultInviteTheme = PartyGlobals.InviteTheme.Valentoons
        if self.partyInfo is not None:
            del self.partyInfo
        activityList = self.partyEditor.partyEditorGrid.getActivitiesOnGrid()
        decorationList = self.partyEditor.partyEditorGrid.getDecorationsOnGrid()
        endTime = self.partyTime + self.partyDuration
        self.partyInfo = PartyInfo(0, 0, self.partyTime.year, self.partyTime.month, self.partyTime.day, self.partyTime.hour, self.partyTime.minute, endTime.year, endTime.month, endTime.day, endTime.hour, endTime.minute, self.isPrivate, defaultInviteTheme, activityList, decorationList, 0)
        if self.noFriends or len(self.getInvitees()) == 0:
            self.inviteVisual.setNoFriends(True)
            self.invitationTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmTitleNoFriends
            self.inviteButton['text'] = TTLocalizer.PartyPlannerInviteButtonNoFriends
            self.selectedInviteThemeLabel.stash()
            self.nextThemeButton.stash()
            self.prevThemeButton.stash()
            self.setInviteTheme(defaultInviteTheme)
        else:
            self.inviteVisual.setNoFriends(False)
            self.invitationTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmTitle
            self.inviteButton['text'] = TTLocalizer.PartyPlannerInviteButton
            self.selectedInviteThemeLabel.unstash()
            self.nextThemeButton.unstash()
            self.prevThemeButton.unstash()
            self.setInviteTheme(defaultInviteTheme)
        self.inviteVisual.updateInvitation(base.localAvatar.getName(), self.partyInfo)
        self.invitationPage.show()
        return

    def __prevTheme(self):
        self.nextThemeButton.show()
        prevTheme = self.currentInvitationTheme - 1
        while prevTheme not in self.inviteThemes:
            prevTheme -= 1
            if prevTheme == self.currentInvitationTheme:
                self.notify.warning('No previous invite theme found.')
                break
            elif prevTheme < 0:
                prevTheme = len(self.inviteVisual.inviteThemesIdToInfo) - 1

        self.setInviteTheme(prevTheme)

    def __nextTheme(self):
        self.prevThemeButton.show()
        nextTheme = self.currentInvitationTheme + 1
        while nextTheme not in self.inviteThemes:
            nextTheme += 1
            if nextTheme == self.currentInvitationTheme:
                self.notify.warning('No next invite theme found.')
                break
            elif nextTheme >= len(self.inviteVisual.inviteThemesIdToInfo):
                nextTheme = 0

        self.setInviteTheme(nextTheme)

    def setInviteTheme(self, themeNumber):
        self.currentInvitationTheme = themeNumber
        self.selectedInviteThemeLabel['text'] = '%s %s (%d/%d)' % (self.inviteVisual.inviteThemesIdToInfo[self.currentInvitationTheme][1],
         TTLocalizer.PartyPlannerInvitationTheme,
         self.inviteThemes.index(self.currentInvitationTheme) + 1,
         len(self.inviteThemes))
        self.partyInfo.inviteTheme = self.currentInvitationTheme
        self.inviteVisual.updateInvitation(base.localAvatar.getName(), self.partyInfo)

    def exitInvitation(self):
        self.invitationPage.hide()
        self.nextButton.show()

    def enterFarewell(self, goingBackAllowed):
        self.farewellPage.show()
        if goingBackAllowed:
            self.prevButton.show()
        else:
            self.prevButton.hide()
        self.nextButton.hide()
        self.partyPlannerHead.reparentTo(self.farewellPage)
        self.partyPlannerHead.startBlink()
        self.partyPlannerHead.startLookAround()
        self.nametagNP.reparentTo(self.farewellPage)
        self.chatNP.reparentTo(self.farewellPage)

    def exitFarewell(self):
        self.farewellPage.hide()
        self.nextButton.show()
        self.prevButton.show()
        self.partyPlannerHead.stopBlink()
        self.partyPlannerHead.stopLookAround()

    def load(self):
        self.frame = DirectFrame(parent=aspect2d, geom=self.gui.find('**/background'), relief=None, scale=0.85, pos=(0.05, 0.0, 0.1))
        self.titleScale = TTLocalizer.PPtitleScale
        self._createNavButtons()
        self.welcomePage = self._createWelcomePage()
        self.welcomePage.hide()
        self.datePage = self._createDatePage()
        self.datePage.hide()
        self.timePage = self._createTimePage()
        self.timePage.hide()
        self.guestPage = self._createGuestPage()
        self.guestPage.hide()
        self.partyEditorPage = self._createPartyEditorPage()
        self.partyEditorPage.hide()
        self.invitationPage = self._createInvitationPage()
        self.invitationPage.hide()
        self.farewellPage = self._createFarewellPage()
        self.farewellPage.hide()
        return

    def _createNavButtons(self):
        self.quitButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/cancelButton_up'), self.gui.find('**/cancelButton_down'), self.gui.find('**/cancelButton_rollover')), command=self.__acceptExit)
        self.nextButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/bottomNext_button/nextButton_up'), self.gui.find('**/bottomNext_button/nextButton_down'), self.gui.find('**/bottomNext_button/nextButton_rollover')), command=self.__nextItem, state=DirectGuiGlobals.DISABLED)
        self.prevButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/bottomPrevious_button/previousButton_up'), self.gui.find('**/bottomPrevious_button/previousButton_down'), self.gui.find('**/bottomPrevious_button/previousButton_rollover')), command=self.__prevItem, state=DirectGuiGlobals.DISABLED)
        self.currentItem = None
        return

    def __createNametag(self, parent):
        if self.nametagGroup == None:
            self.nametagGroup = NametagGroup()
            self.nametagGroup.setFont(OTPGlobals.getInterfaceFont())
            self.nametagGroup.setActive(0)
            self.nametagGroup.setAvatar(self.partyPlannerHead)
            self.nametagGroup.manage(base.marginManager)
            self.nametagGroup.setColorCode(self.nametagGroup.CCNonPlayer)
            self.nametagGroup.getNametag2d().setContents(0)
            self.nametagNode = NametagFloat2d()
            self.nametagNode.setContents(Nametag.CName)
            self.nametagGroup.addNametag(self.nametagNode)
            self.nametagGroup.setName(base.cr.partyManager.getPartyPlannerName())
            self.nametagNP = parent.attachNewNode(self.nametagNode.upcastToPandaNode())
            nametagPos = self.gui.find('**/step_01_partymanPeteNametag_locator').getPos()
            self.nametagNP.setPosHprScale(nametagPos[0], 0, nametagPos[2], 0, 0, 0, 0.1, 1, 0.1)
            self.chatNode = NametagFloat2d()
            self.chatNode.setContents(Nametag.CSpeech | Nametag.CThought)
            self.nametagGroup.addNametag(self.chatNode)
            self.nametagGroup.setChat(TTLocalizer.PartyPlannerInstructions, CFSpeech)
            self.chatNP = parent.attachNewNode(self.chatNode.upcastToPandaNode())
            chatPos = self.gui.find('**/step_01_partymanPeteText_locator').getPos()
            self.chatNP.setPosHprScale(chatPos[0], 0, chatPos[2], 0, 0, 0, 0.08, 1, 0.08)
        return

    def clearNametag(self):
        if self.nametagGroup != None:
            self.nametagGroup.unmanage(base.marginManager)
            self.nametagGroup.removeNametag(self.nametagNode)
            self.nametagGroup.removeNametag(self.chatNode)
            self.nametagNP.removeNode()
            self.chatNP.removeNode()
            del self.nametagNP
            del self.chatNP
            del self.nametagNode
            del self.chatNode
            self.nametagGroup.setAvatar(NodePath())
            self.nametagGroup = None
        return

    def _createWelcomePage(self):
        self.nametagGroup = None
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerWelcomePage')
        self.welcomeTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerWelcomeTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.partyPlannerHead = ToonHead.ToonHead()
        partyPlannerStyle = base.cr.partyManager.getPartyPlannerStyle()
        self.partyPlannerHead.setupHead(partyPlannerStyle, forGui=True)
        self.partyPlannerHead.setPos(self.gui.find('**/step_01_partymanPete_locator').getPos())
        animal = partyPlannerStyle.getAnimal()
        if animal == 'cat' or animal == 'pig':
            headScale = 0.4
        elif animal == 'dog' or animal == 'bear':
            headScale = 0.45
        elif animal == 'rabbit':
            headScale = 0.35
        else:
            headScale = 0.3
        self.partyPlannerHead.setScale(headScale)
        self.partyPlannerHead.setH(180.0)
        self.partyPlannerHead.reparentTo(page)
        self.__createNametag(page)
        return page

    def _createDatePage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerDatePage')
        self.createDateTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerDateTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        pos = self.gui.find('**/step_06_sendInvitation_locator').getPos()
        self.makePartyNowButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/send_up'), self.gui.find('**/send_down'), self.gui.find('**/send_rollover')), text=TTLocalizer.PartyPlannerPartyNow, text_pos=(pos[0], pos[2]), text_scale=0.05, command=self.__doMakePartyNow)
        curServerDate = base.cr.toontownTimeManager.getCurServerDateTime()
        self.calendarGuiMonth = CalendarGuiMonth(page, curServerDate, scale=0.95, pos=(-0.05, 0.0, -0.33), dayClickCallback=self._dayClickCallback, onlyFutureDaysClickable=True)
        return page

    def __doMakePartyNow(self):
        self.request('Invitation')

    def _dayClickCallback(self, calendarGuiDay):
        self.selectedCalendarGuiDay = calendarGuiDay
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.makePartyNowButton.hide()
        self.nextButton.show()

    def alterPartyTime(self, year = None, month = None, day = None, hour = None, minute = None):
        self.partyTime = datetime(year=self.positiveTime('year', year), month=self.positiveTime('month', month), day=self.positiveTime('day', day), hour=self.positiveTime('hour', hour), minute=self.positiveTime('minute', minute), tzinfo=self.partyTime.tzinfo)

    def positiveTime(self, type, amount):
        if amount is None:
            return getattr(self.partyTime, type)
        if type == 'hour' or type == 'minute':
            if amount < 0:
                return self.timeTypeToMaxValue[type] + 1 + self.timeTypeToChangeAmount[type][1]
            elif amount > self.timeTypeToMaxValue[type]:
                return 0
        return amount

    def _createTimePage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerTimePage')
        self.createTimeTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.clockImage = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/toontownTime_background'))
        self.timePageToontownLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeToontown, pos=self.gui.find('**/step_03_toontown_locator').getPos(), scale=0.15, text_fg=(1.0, 0.0, 0.0, 1.0), text_font=ToontownGlobals.getSignFont())
        self.timePageTimeLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeTime, pos=self.gui.find('**/step_03_time_locator').getPos(), scale=0.15, text_fg=(1.0, 0.0, 0.0, 1.0), text_font=ToontownGlobals.getSignFont())
        self.timePageRecapLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeRecap, pos=self.gui.find('**/step_03_partyDateAndTime_locator').getPos(), scale=0.09)
        self.timePageRecapToontownTimeLabel1 = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeToontownTime, pos=self.gui.find('**/step_03_toontownTime_locator').getPos(), scale=0.06)
        self.timePageRecapToontownTimeLabel2 = DirectLabel(parent=page, relief=None, text='%s' % PartyUtils.formatDateTime(self.partyTime), pos=self.gui.find('**/step_03_toontownDateAndTime_loactor').getPos(), textMayChange=True, scale=0.06)
        self.timePageRecapLocalTimeLabel = DirectLabel(parent=page, relief=None, text='%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True)), pos=self.gui.find('**/step_03_localDateAndTime_loactor').getPos(), textMayChange=True, scale=0.06, text_fg=(1.0, 0.0, 0.0, 1.0))
        self.timeInputHourLabel, self.timeInputHourUpButton, self.timeInputHourDownButton = self.getTimeWidgets(page, 'hour')
        self.timeInputMinuteLabel, self.timeInputMinuteUpButton, self.timeInputMinuteDownButton = self.getTimeWidgets(page, 'minute')
        self.timeInputAmPmLabel, self.timeInputAmPmUpButton, self.timeInputAmPmDownButton = self.getTimeWidgets(page, 'ampm')
        self.timePagecolonLabel = DirectLabel(parent=page, relief=None, text=':', pos=self.gui.find('**/step_03_colon_locator').getPos(), scale=0.15)
        return page

    def getTimeWidgets(self, page, type):
        if type == 'ampm':
            data = self.getCurrentAmPm()
        else:
            data = getattr(self.partyTime, type)
            if data == 0 and type == 'minute':
                data = '00'
            else:
                if type == 'hour':
                    data = data % 12
                    if data == 0:
                        data = 12
                data = '%d' % data
        label = DirectLabel(parent=page, relief=None, text='%s' % data, textMayChange=True, pos=self.gui.find('**/step_03_%s_locator' % type).getPos(), scale=0.12)

        def changeValue(self, amount):
            if type == 'ampm':
                self.alterPartyTime(hour=(self.partyTime.hour + 12) % 24)
                newAmount = self.getCurrentAmPm()
                label['text'] = newAmount
            else:
                if type == 'hour':
                    newAmount = getattr(self.partyTime, type) + amount
                    newAmount = newAmount % 12
                    if self.timeInputAmPmLabel['text'] == TTLocalizer.PartyTimeFormatMeridiemPM:
                        newAmount = newAmount % 12 + 12
                    self.alterPartyTime(hour=newAmount)
                elif type == 'minute':
                    newAmount = getattr(self.partyTime, type) + amount
                    self.alterPartyTime(minute=newAmount)
                else:
                    PartyPlanner.notify.error('Invalid type for changeValue in PartyPlanner: %s' % type)
                newAmount = getattr(self.partyTime, type)
                if newAmount < 10 and type == 'minute':
                    label['text'] = '0%d' % newAmount
                else:
                    if type == 'hour':
                        newAmount = newAmount % 12
                        if newAmount == 0:
                            newAmount = 12
                    label['text'] = '%d' % newAmount
            self.timePageRecapToontownTimeLabel2['text'] = '%s' % PartyUtils.formatDateTime(self.partyTime)
            self.timePageRecapLocalTimeLabel['text'] = '%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True))

        upButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/%sButtonUp_up' % type), self.gui.find('**/%sButtonUp_down' % type), self.gui.find('**/%sButtonUp_rollover' % type)), command=changeValue, extraArgs=[self, self.timeTypeToChangeAmount[type][0]])
        downButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/%sButtonDown_up' % type), self.gui.find('**/%sButtonDown_down' % type), self.gui.find('**/%sButtonDown_rollover' % type)), command=changeValue, extraArgs=[self, self.timeTypeToChangeAmount[type][1]])
        return (label, upButton, downButton)

    def getCurrentAmPm(self):
        if self.partyTime.hour < 12:
            return TTLocalizer.PartyTimeFormatMeridiemAM
        else:
            return TTLocalizer.PartyTimeFormatMeridiemPM

    def _createGuestPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerGuestPage')
        self.guestTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerGuestTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.guestBackgroundLabel = DirectLabel(parent=page, relief=None, image=self.gui.find('**/guestListBackground_flat'), scale=(1.2, 1.0, 1.0))
        self.friendList = ScrolledFriendList(page, self.gui, makeItemsCheckBoxes=True)
        if len(base.localAvatar.friendsList) == 0:
            self.noFriends = True
        else:
            self.noFriends = False
            for friendPair in base.localAvatar.friendsList:
                self.friendList.addFriend(determineFriendName(friendPair), friendPair[0])

            self.friendList.scrollTo(0)
        pos = self.gui.find('**/step_04_partyWillBe_locator').getPos()
        self.publicPrivateLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPublicPrivateLabel, text_align=TextNode.ACenter, text_scale=0.065, pos=pos)
        self.publicDescriptionLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPublicDescription, text_align=TextNode.ACenter, text_scale=TTLocalizer.PPpbulicDescriptionLabel, pos=(pos[0] - 0.52, pos[1], pos[2]))
        self.publicDescriptionLabel.stash()
        self.privateDescriptionLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPrivateDescription, text_align=TextNode.ACenter, text_scale=TTLocalizer.PPprivateDescriptionLabel, pos=(pos[0] + 0.55, pos[1], pos[2]))
        self.privateDescriptionLabel.stash()
        pos = self.gui.find('**/step_04_public_locator').getPos()
        self.publicButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/publicButton_up'),
         self.gui.find('**/publicButton_down'),
         self.gui.find('**/publicButton_rollover'),
         self.gui.find('**/publicButton_inactive')), text=TTLocalizer.PartyPlannerPublic, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPpublicButton, command=self.__doTogglePublicPrivate)
        self.publicButton['state'] = DirectGuiGlobals.DISABLED
        self.publicButton.bind(DirectGuiGlobals.ENTER, self.__enterPublic)
        self.publicButton.bind(DirectGuiGlobals.EXIT, self.__exitPublic)
        pos = self.gui.find('**/step_04_private_locator').getPos()
        self.privateButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/privateButton_up'),
         self.gui.find('**/privateButton_down'),
         self.gui.find('**/privateButton_rollover'),
         self.gui.find('**/privateButton_inactive')), text=TTLocalizer.PartyPlannerPrivate, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPprivateButton, command=self.__doTogglePublicPrivate)
        self.privateButton.bind(DirectGuiGlobals.ENTER, self.__enterPrivate)
        self.privateButton.bind(DirectGuiGlobals.EXIT, self.__exitPrivate)
        self.checkAllButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/checkAllButton_up'), self.gui.find('**/checkAllButton_down'), self.gui.find('**/checkAllButton_rollover')), command=self.__doCheckAll)
        self.uncheckAllButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/uncheckAllButton_up'), self.gui.find('**/uncheckAllButton_down'), self.gui.find('**/uncheckAllButton_rollover')), command=self.__doUncheckAll)
        return page

    def __doCheckAll(self):
        for friendBox in self.friendList['items']:
            friendBox['indicatorValue'] = True

    def __doUncheckAll(self):
        for friendBox in self.friendList['items']:
            friendBox['indicatorValue'] = False

    def __enterPrivate(self, mouseEvent):
        self.privateDescriptionLabel.unstash()

    def __exitPrivate(self, mouseEvent):
        self.privateDescriptionLabel.stash()

    def __enterPublic(self, mouseEvent):
        self.publicDescriptionLabel.unstash()

    def __exitPublic(self, mouseEvent):
        self.publicDescriptionLabel.stash()

    def __doTogglePublicPrivate(self):
        if self.isPrivate:
            self.isPrivate = False
            self.privateButton['state'] = DirectGuiGlobals.NORMAL
            self.publicButton['state'] = DirectGuiGlobals.DISABLED
        else:
            self.isPrivate = True
            self.privateButton['state'] = DirectGuiGlobals.DISABLED
            self.publicButton['state'] = DirectGuiGlobals.NORMAL

    def _createPartyEditorPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerEditorPage')
        self.LayoutTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerEditorTitle, pos=self.gui.find('**/title_locator').getPos() + Point3(0.0, 0.0, 0.075), scale=self.titleScale)
        self.costLabel = DirectLabel(parent=page, pos=(-0.74, 0.0, 0.17), relief=None, text=TTLocalizer.PartyPlannerTotalCost % 0, text_align=TextNode.ACenter, scale=TTLocalizer.PPcostLabel, textMayChange=True)
        self.partyGridBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/partyGrid_flat'))
        self.partyGroundsLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPartyGrounds, text_font=ToontownGlobals.getSignFont(), text_fg=VBase4(1.0, 0.0, 0.0, 1.0), text_scale=TTLocalizer.PPpartyGroundsLabel, pos=self.gui.find('**/step_05_partyGrounds_text_locator').getPos(), scale=0.1)
        self.activityBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/activitiesDecorations_flat1'), pos=(0.0, 0.0, 0.04))
        pos = self.gui.find('**/step_05_instructions_locator').getPos()
        self.instructionLabel = DirectLabel(parent=page, relief=None, text=' ', text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPinstructionLabel, textMayChange=True, geom=self.gui.find('**/instructions_flat'))
        self.elementTitleLabel = DirectLabel(parent=page, relief=None, text=' ', pos=self.gui.find('**/step_05_activitiesName_text_locator').getPos() + Point3(0.0, 0.0, 0.04), text_scale=TTLocalizer.PPelementTitleLabel, textMayChange=True)
        self.elementPriceNode = TextNode('ElementPrice')
        self.elementPriceNode.setAlign(TextNode.ALeft)
        self.elementPriceNode.setTextColor(0.0, 0.0, 0.0, 1.0)
        self.elementPriceNode.setFont(ToontownGlobals.getToonFont())
        self.elementPrice = page.attachNewNode(self.elementPriceNode)
        self.elementPrice.setScale(TTLocalizer.PPelementPriceNode)
        self.elementPrice.setPos(self.gui.find('**/step_05_activityPrice_text_locator').getPos() + Point3(-0.02, 0.0, 0.04))
        self.elementDescriptionNode = TextNode('ElementDescription')
        self.elementDescriptionNode.setAlign(TextNode.ACenter)
        self.elementDescriptionNode.setWordwrap(8)
        self.elementDescriptionNode.setFont(ToontownGlobals.getToonFont())
        self.elementDescriptionNode.setTextColor(0.0, 0.0, 0.0, 1.0)
        self.elementDescription = page.attachNewNode(self.elementDescriptionNode)
        self.elementDescription.setScale(TTLocalizer.PPelementDescription)
        self.elementDescription.setPos(self.gui.find('**/step_05_activityDescription_text_locator').getPos() + Point3(0.0, 0.0, 0.04))
        self.totalMoney = base.localAvatar.getTotalMoney()
        catalogGui = loader.loadModel('phase_5.5/models/gui/catalog_gui')
        self.beanBank = DirectLabel(parent=page, relief=None, text=str(self.totalMoney), text_align=TextNode.ARight, text_scale=0.075, text_fg=(0.95, 0.95, 0, 1), text_shadow=(0, 0, 0, 1), text_pos=(0.495, -0.53), text_font=ToontownGlobals.getSignFont(), textMayChange=True, image=catalogGui.find('**/bean_bank'), image_scale=(0.65, 0.65, 0.65), scale=0.9, pos=(-0.75, 0.0, 0.6))
        catalogGui.removeNode()
        del catalogGui
        self.accept(localAvatar.uniqueName('moneyChange'), self.__moneyChange)
        self.accept(localAvatar.uniqueName('bankMoneyChange'), self.__moneyChange)
        self.partyEditor = PartyEditor(self, page)
        self.partyEditor.request('Hidden')
        pos = self.gui.find('**/step_05_add_text_locator').getPos()
        self.elementBuyButton = DirectButton(parent=page, relief=None, text=TTLocalizer.PartyPlannerBuy, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPelementBuyButton, geom=(self.gui.find('**/add_up'), self.gui.find('**/add_down'), self.gui.find('**/add_rollover')), geom3_color=VBase4(0.5, 0.5, 0.5, 1.0), textMayChange=True, pos=(0.0, 0.0, 0.04), command=self.partyEditor.buyCurrentElement)
        self.okWithPartyGroundsLayoutEvent = 'okWithPartyGroundsLayoutEvent'
        self.accept(self.okWithPartyGroundsLayoutEvent, self.okWithPartyGroundsLayout)
        self.okWithGroundsGui = TTDialog.TTGlobalDialog(dialogName=self.uniqueName('PartyEditorOkGui'), doneEvent=self.okWithPartyGroundsLayoutEvent, message=TTLocalizer.PartyPlannerOkWithGroundsLayout, style=TTDialog.YesNo, okButtonText=OTPLocalizer.DialogYes, cancelButtonText=OTPLocalizer.DialogNo)
        self.okWithGroundsGui.doneStatus = ''
        self.okWithGroundsGui.hide()
        return page

    def okWithPartyGroundsLayout(self):
        self.okWithGroundsGui.hide()
        if self.okWithGroundsGui.doneStatus == 'ok':
            self.__nextItem()

    def setNextButtonState(self, enabled):
        if enabled:
            self.nextButton['state'] = DirectGuiGlobals.NORMAL
            self.nextButton.show()
        else:
            self.nextButton['state'] = DirectGuiGlobals.DISABLED
            self.nextButton.hide()

    def _createInvitationPage(self):
        self.__handleHolidays()
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerInvitationPage')
        self.invitationTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerConfirmTitle, textMayChange=True, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.invitationBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/invitationBackground'))
        self.inviteVisual = InviteVisual(page)
        self.selectedInviteThemeLabel = DirectLabel(parent=page, relief=None, pos=self.gui.find('**/step_06_theme_locator').getPos(), text='', text_scale=0.06, textMayChange=True)
        self.nextThemeButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/topNext_button/nextButton_up'), self.gui.find('**/topNext_button/nextButton_down'), self.gui.find('**/topNext_button/nextButton_rollover')), command=self.__nextTheme)
        self.prevThemeButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/topPrevious_button/previousButton_up'), self.gui.find('**/topPrevious_button/previousButton_down'), self.gui.find('**/topPrevious_button/previousButton_rollover')), command=self.__prevTheme)
        pos = self.gui.find('**/step_06_sendInvitation_locator').getPos()
        self.inviteButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/send_up'), self.gui.find('**/send_down'), self.gui.find('**/send_rollover')), text=TTLocalizer.PartyPlannerInviteButton, textMayChange=True, text_scale=0.05, text_pos=(pos[0], pos[2]), command=self.__handleComplete)
        return page

    def __handleHolidays(self):
        self.inviteThemes = range(len(PartyGlobals.InviteTheme))
        if hasattr(base.cr, 'newsManager') and base.cr.newsManager:
            holidayIds = base.cr.newsManager.getHolidayIdList()
            if ToontownGlobals.VALENTINES_DAY not in holidayIds:
                self.inviteThemes.remove(PartyGlobals.InviteTheme.Valentoons)
            if ToontownGlobals.VICTORY_PARTY_HOLIDAY not in holidayIds:
                self.inviteThemes.remove(PartyGlobals.InviteTheme.VictoryParty)
            if ToontownGlobals.WINTER_DECORATIONS not in holidayIds and ToontownGlobals.WACKY_WINTER_DECORATIONS not in holidayIds:
                self.inviteThemes.remove(PartyGlobals.InviteTheme.Winter)

    def _createFarewellPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerFarewellPage')
        self.confirmTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerConfirmationAllOkTitle, textMayChange=True, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        pos = self.gui.find('**/step_07_close_text_locator').getPos()
        self.closePlannerButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/close_up'), self.gui.find('**/close_down'), self.gui.find('**/close_rollover')), text=TTLocalizer.PartyPlannerClosePlanner, text_scale=0.055, text_pos=(pos[0], pos[2]), command=self.__acceptExit)
        return page

    def close(self):
        self.ignore('addPartyResponseReceived')
        self.ignore(localAvatar.uniqueName('moneyChange'))
        self.ignore(localAvatar.uniqueName('bankMoneyChange'))
        self.timeInputHourUpButton.destroy()
        self.timeInputHourDownButton.destroy()
        self.timeInputMinuteUpButton.destroy()
        self.timeInputMinuteDownButton.destroy()
        self.timeInputAmPmUpButton.destroy()
        self.timeInputAmPmDownButton.destroy()
        self.privateButton.destroy()
        self.publicButton.destroy()
        self.makePartyNowButton.destroy()
        self.checkAllButton.destroy()
        self.uncheckAllButton.destroy()
        self.elementBuyButton.destroy()
        self.nextThemeButton.destroy()
        self.prevThemeButton.destroy()
        self.inviteButton.destroy()
        self.closePlannerButton.destroy()
        self.ignore(self.okWithPartyGroundsLayoutEvent)
        if hasattr(self, 'okWithGroundsGui'):
            self.okWithGroundsGui.cleanup()
            del self.okWithGroundsGui
        if hasattr(self, 'frame') and not self.frame.isEmpty():
            messenger.send(self.doneEvent)
            self.hide()
            self.cleanup()
            self.friendList.removeAndDestroyAllItems()
            self.friendList.destroy()
            self.calendarGuiMonth.destroy()
            self.frame.destroy()
        self.partyPlannerHead.delete()
        self.partyPlannerHead.removeNode()
        self.clearNametag()
        self.partyEditor.request('Cleanup')
        self.partyEditor = None
        self.destroy()
        del self
        return

    def __handleComplete(self):
        self.inviteButton['state'] = DirectGuiGlobals.DISABLED
        self.prevButton['state'] = DirectGuiGlobals.DISABLED
        endTime = self.partyTime + self.partyDuration
        hostId = base.localAvatar.doId
        self.partyActivities = self.partyEditor.partyEditorGrid.getActivitiesOnGrid()
        decorations = self.partyEditor.partyEditorGrid.getDecorationsOnGrid()
        invitees = self.getInvitees()
        self.accept('addPartyResponseReceived', self.processAddPartyResponse)
        base.cr.partyManager.sendAddParty(hostId, self.partyTime.strftime('%Y-%m-%d %H:%M:%S'), endTime.strftime('%Y-%m-%d %H:%M:%S'), self.isPrivate, self.currentInvitationTheme, self.partyActivities, decorations, invitees)

    def getInvitees(self):
        invitees = []
        for friendBox in self.friendList['items']:
            if friendBox['indicatorValue']:
                invitees.append(friendBox.getPythonTag('id'))

        return invitees

    def processAddPartyResponse(self, hostId, errorCode):
        PartyPlanner.notify.debug('processAddPartyResponse : hostId=%d errorCode=%s' % (hostId, PartyGlobals.AddPartyErrorCode.getString(errorCode)))
        goingBackAllowed = False
        if errorCode == PartyGlobals.AddPartyErrorCode.AllOk:
            goingBackAllowed = False
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationAllOkTitle
            if self.noFriends or len(self.getInvitees()) == 0:
                confirmRecapText = TTLocalizer.PartyPlannerConfirmationAllOkTextNoFriends
            else:
                confirmRecapText = TTLocalizer.PartyPlannerConfirmationAllOkText
        elif errorCode == PartyGlobals.AddPartyErrorCode.ValidationError:
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationValidationErrorText
        elif errorCode == PartyGlobals.AddPartyErrorCode.DatabaseError:
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationDatabaseErrorText
        elif errorCode == PartyGlobals.AddPartyErrorCode.TooManyHostedParties:
            goingBackAllowed = False
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationTooManyText
        self.nametagGroup.setChat(confirmRecapText, CFSpeech)
        self.request('Farewell', goingBackAllowed)

    def __acceptExit(self):
        PartyPlanner.notify.debug('__acceptExit')
        if hasattr(self, 'frame'):
            self.hide()
            messenger.send(self.doneEvent)

    def __nextItem(self):
        messenger.send('wakeup')
        if self.state == 'PartyEditor' and self.okWithGroundsGui.doneStatus != 'ok':
            self.okWithGroundsGui.show()
            return
        if self.state == 'PartyEditor' and self.noFriends:
            self.request('Date')
            self.selectedCalendarGuiDay = None
            self.calendarGuiMonth.clearSelectedDay()
            return
        if self.state == 'Guests':
            self.selectedCalendarGuiDay = None
            self.calendarGuiMonth.clearSelectedDay()
        if self.state == 'Time':
            if self.partyTime < base.cr.toontownTimeManager.getCurServerDateTime():
                self.okChooseFutureTimeEvent = 'okChooseFutureTimeEvent'
                self.acceptOnce(self.okChooseFutureTimeEvent, self.okChooseFutureTime)
                self.chooseFutureTimeDialog = TTDialog.TTGlobalDialog(dialogName=self.uniqueName('chooseFutureTimeDialog'), doneEvent=self.okChooseFutureTimeEvent, message=TTLocalizer.PartyPlannerChooseFutureTime, style=TTDialog.Acknowledge)
                self.chooseFutureTimeDialog.show()
                return
        self.requestNext()
        return

    def okChooseFutureTime(self):
        if hasattr(self, 'chooseFutureTimeDialog'):
            self.chooseFutureTimeDialog.cleanup()
            del self.chooseFutureTimeDialog
        if hasattr(self, 'okChooseFutureTimeEvent'):
            self.ignore(self.okChooseFutureTimeEvent)

    def __prevItem(self):
        messenger.send('wakeup')
        if self.state == 'Date' and self.noFriends:
            self.request('PartyEditor')
            return
        if self.state == 'Invitation' and self.selectedCalendarGuiDay is None:
            self.request('Guests')
            return
        self.requestPrev()
        return

    def __moneyChange(self, newMoney):
        if hasattr(self, 'totalMoney'):
            self.totalMoney = base.localAvatar.getTotalMoney()
        if hasattr(self, 'beanBank'):
            self.beanBank['text'] = str(int(self.totalMoney))
Пример #2
0
class PartyPlanner(DirectFrame, FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('PartyPlanner')

    def __init__(self, doneEvent = None):
        FSM.__init__(self, 'PartyPlannerFSM')
        DirectFrame.__init__(self)
        self.doneEvent = doneEvent
        self.stateArray = ['Off',
         'Welcome',
         'PartyEditor',
         'Guests',
         'Date',
         'Time',
         'Invitation',
         'Farewell']
        self.partyTime = base.cr.toontownTimeManager.getCurServerDateTime()
        self.partyNowTime = base.cr.toontownTimeManager.getCurServerDateTime()
        minutesToNextFifteen = 15 - self.partyTime.minute % 15
        self.cleanPartyTime = self.partyTime + timedelta(minutes=minutesToNextFifteen, seconds=-self.partyTime.second)
        self.partyTime = self.cleanPartyTime
        self.guests = []
        self.isPrivate = False
        self.selectedCalendarGuiDay = None
        self.gui = loader.loadModel('phase_4/models/parties/partyPlannerGUI')
        self.partyDuration = timedelta(hours=PartyGlobals.DefaultPartyDuration)
        self.timeTypeToMaxValue = {'hour': 23,
         'minute': 59}
        self.timeTypeToChangeAmount = {'hour': (1, -1),
         'minute': (15, -15),
         'ampm': (1, -1)}
        self.partyInfo = None
        self.asapMinuteRounding = base.config.GetInt('party-asap-minute-rounding', PartyGlobals.PartyPlannerAsapMinuteRounding)
        self.load()
        self.request('Welcome')
        return

    def enterWelcome(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.DISABLED
        self.prevButton.hide()
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.welcomePage.show()
        self.partyPlannerHead.reparentTo(self.welcomePage)
        self.partyPlannerHead.startBlink()
        self.partyPlannerHead.startLookAround()
        self.nametagNP.reparentTo(self.welcomePage)
        self.chatNP.reparentTo(self.welcomePage)

    def exitWelcome(self):
        self.welcomePage.hide()
        self.prevButton.show()
        self.partyPlannerHead.stopBlink()
        self.partyPlannerHead.stopLookAround()

    def enterPartyEditor(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton['state'] = DirectGuiGlobals.DISABLED
        self.nextButton.hide()
        self.partyEditorPage.show()
        self.okWithGroundsGui.doneStatus = ''
        self.partyEditor.request('Idle')

    def exitPartyEditor(self):
        self.partyEditor.request('Hidden')
        self.partyEditorPage.hide()

    def enterGuests(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.show()
        self.guestPage.show()

    def exitGuests(self):
        self.guests = []
        for friendCheckBox in self.friendList['items']:
            if friendCheckBox['indicatorValue']:
                self.guests.append(friendCheckBox.getPythonTag('id'))

        self.guestPage.hide()

    def enterDate(self, *args):
        self.prevButton.show()
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        if self.selectedCalendarGuiDay is None:
            self.nextButton['state'] = DirectGuiGlobals.DISABLED
            self.nextButton.hide()
            self.makePartyNowButton.show()
        self.datePage.show()
        return

    def exitDate(self):
        self.datePage.hide()
        self.nextButton.show()
        if self.selectedCalendarGuiDay is not None:
            self.partyTime = self.cleanPartyTime
            self.alterPartyTime(year=self.selectedCalendarGuiDay.myDate.year, month=self.selectedCalendarGuiDay.myDate.month, day=self.selectedCalendarGuiDay.myDate.day)
        else:
            self.partyNowTime = self.calcAsapTime()
            self.partyTime = self.partyNowTime
        return

    def calcAsapTime(self):
        curServerTime = base.cr.toontownTimeManager.getCurServerDateTime()
        baseTime = curServerTime
        baseTime = baseTime.replace(baseTime.year, baseTime.month, baseTime.day, baseTime.hour, baseTime.minute, second=0, microsecond=0)
        minute = curServerTime.minute
        remainder = minute % self.asapMinuteRounding
        if remainder:
            baseTime += timedelta(minutes=self.asapMinuteRounding - remainder)
        else:
            baseTime += timedelta(minutes=self.asapMinuteRounding)
        return baseTime

    def enterTime(self, *args):
        self.prevButton.show()
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.show()
        self.timePage.show()
        self.timePageRecapToontownTimeLabel2['text'] = '%s' % PartyUtils.formatDateTime(self.partyTime)
        self.timePageRecapLocalTimeLabel['text'] = '%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True))

    def exitTime(self):
        self.timePage.hide()
        self.nextButton.show()

    def enterInvitation(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.hide()
        defaultInviteTheme = PartyGlobals.InviteTheme.GenericMale
        if hasattr(base.cr, 'newsManager') and base.cr.newsManager:
            if ToontownGlobals.VICTORY_PARTY_HOLIDAY in base.cr.newsManager.getHolidayIdList():
                defaultInviteTheme = PartyGlobals.InviteTheme.VictoryParty
            elif ToontownGlobals.KARTING_TICKETS_HOLIDAY in base.cr.newsManager.getHolidayIdList() or ToontownGlobals.CIRCUIT_RACING_EVENT in base.cr.newsManager.getHolidayIdList():
                defaultInviteTheme = PartyGlobals.InviteTheme.Racing
            elif ToontownGlobals.VALENTINES_DAY in base.cr.newsManager.getHolidayIdList():
                defaultInviteTheme = PartyGlobals.InviteTheme.Valentoons
        if self.partyInfo is not None:
            del self.partyInfo
        activityList = self.partyEditor.partyEditorGrid.getActivitiesOnGrid()
        decorationList = self.partyEditor.partyEditorGrid.getDecorationsOnGrid()
        endTime = self.partyTime + self.partyDuration
        self.partyInfo = PartyInfo(0, 0, self.partyTime.year, self.partyTime.month, self.partyTime.day, self.partyTime.hour, self.partyTime.minute, endTime.year, endTime.month, endTime.day, endTime.hour, endTime.minute, self.isPrivate, defaultInviteTheme, activityList, decorationList, 0)
        if self.noFriends or len(self.getInvitees()) == 0:
            self.inviteVisual.setNoFriends(True)
            self.invitationTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmTitleNoFriends
            self.inviteButton['text'] = TTLocalizer.PartyPlannerInviteButtonNoFriends
            self.selectedInviteThemeLabel.stash()
            self.nextThemeButton.stash()
            self.prevThemeButton.stash()
            self.setInviteTheme(defaultInviteTheme)
        else:
            self.inviteVisual.setNoFriends(False)
            self.invitationTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmTitle
            self.inviteButton['text'] = TTLocalizer.PartyPlannerInviteButton
            self.selectedInviteThemeLabel.unstash()
            self.nextThemeButton.unstash()
            self.prevThemeButton.unstash()
            self.setInviteTheme(defaultInviteTheme)
        self.inviteVisual.updateInvitation(base.localAvatar.getName(), self.partyInfo)
        self.invitationPage.show()
        return

    def __prevTheme(self):
        self.nextThemeButton.show()
        prevTheme = self.currentInvitationTheme - 1
        while prevTheme not in self.inviteThemes:
            prevTheme -= 1
            if prevTheme == self.currentInvitationTheme:
                self.notify.warning('No previous invite theme found.')
                break
            elif prevTheme < 0:
                prevTheme = len(self.inviteVisual.inviteThemesIdToInfo) - 1

        self.setInviteTheme(prevTheme)

    def __nextTheme(self):
        self.prevThemeButton.show()
        nextTheme = self.currentInvitationTheme + 1
        while nextTheme not in self.inviteThemes:
            nextTheme += 1
            if nextTheme == self.currentInvitationTheme:
                self.notify.warning('No next invite theme found.')
                break
            elif nextTheme >= len(self.inviteVisual.inviteThemesIdToInfo):
                nextTheme = 0

        self.setInviteTheme(nextTheme)

    def setInviteTheme(self, themeNumber):
        self.currentInvitationTheme = themeNumber
        self.selectedInviteThemeLabel['text'] = '%s %s (%d/%d)' % (self.inviteVisual.inviteThemesIdToInfo[self.currentInvitationTheme][1],
         TTLocalizer.PartyPlannerInvitationTheme,
         self.inviteThemes.index(self.currentInvitationTheme) + 1,
         len(self.inviteThemes))
        self.partyInfo.inviteTheme = self.currentInvitationTheme
        self.inviteVisual.updateInvitation(base.localAvatar.getName(), self.partyInfo)

    def exitInvitation(self):
        self.invitationPage.hide()
        self.nextButton.show()

    def enterFarewell(self, goingBackAllowed):
        self.farewellPage.show()
        if goingBackAllowed:
            self.prevButton.show()
        else:
            self.prevButton.hide()
        self.nextButton.hide()
        self.partyPlannerHead.reparentTo(self.farewellPage)
        self.partyPlannerHead.startBlink()
        self.partyPlannerHead.startLookAround()
        self.nametagNP.reparentTo(self.farewellPage)
        self.chatNP.reparentTo(self.farewellPage)

    def exitFarewell(self):
        self.farewellPage.hide()
        self.nextButton.show()
        self.prevButton.show()
        self.partyPlannerHead.stopBlink()
        self.partyPlannerHead.stopLookAround()

    def load(self):
        self.frame = DirectFrame(parent=aspect2d, geom=self.gui.find('**/background'), relief=None, scale=0.85, pos=(0.05, 0.0, 0.1))
        self.titleScale = TTLocalizer.PPtitleScale
        self._createNavButtons()
        self.welcomePage = self._createWelcomePage()
        self.welcomePage.hide()
        self.datePage = self._createDatePage()
        self.datePage.hide()
        self.timePage = self._createTimePage()
        self.timePage.hide()
        self.guestPage = self._createGuestPage()
        self.guestPage.hide()
        self.partyEditorPage = self._createPartyEditorPage()
        self.partyEditorPage.hide()
        self.invitationPage = self._createInvitationPage()
        self.invitationPage.hide()
        self.farewellPage = self._createFarewellPage()
        self.farewellPage.hide()
        return

    def _createNavButtons(self):
        self.quitButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/cancelButton_up'), self.gui.find('**/cancelButton_down'), self.gui.find('**/cancelButton_rollover')), command=self.__acceptExit)
        self.nextButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/bottomNext_button/nextButton_up'), self.gui.find('**/bottomNext_button/nextButton_down'), self.gui.find('**/bottomNext_button/nextButton_rollover')), command=self.__nextItem, state=DirectGuiGlobals.DISABLED)
        self.prevButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/bottomPrevious_button/previousButton_up'), self.gui.find('**/bottomPrevious_button/previousButton_down'), self.gui.find('**/bottomPrevious_button/previousButton_rollover')), command=self.__prevItem, state=DirectGuiGlobals.DISABLED)
        self.currentItem = None
        return

    def __createNametag(self, parent):
        if self.nametagGroup == None:
            self.nametagGroup = NametagGroup()
            self.nametagGroup.setFont(OTPGlobals.getInterfaceFont())
            self.nametagGroup.setActive(0)
            self.nametagGroup.setAvatar(self.partyPlannerHead)
            self.nametagGroup.manage(base.marginManager)
            self.nametagGroup.setColorCode(self.nametagGroup.CCNonPlayer)
            self.nametagGroup.getNametag2d().setContents(0)
            self.nametagNode = NametagFloat2d()
            self.nametagNode.setContents(Nametag.CName)
            self.nametagGroup.addNametag(self.nametagNode)
            self.nametagGroup.setName(base.cr.partyManager.getPartyPlannerName())
            self.nametagNP = parent.attachNewNode(self.nametagNode.upcastToPandaNode())
            nametagPos = self.gui.find('**/step_01_partymanPeteNametag_locator').getPos()
            self.nametagNP.setPosHprScale(nametagPos[0], 0, nametagPos[2], 0, 0, 0, 0.1, 1, 0.1)
            self.chatNode = NametagFloat2d()
            self.chatNode.setContents(Nametag.CSpeech | Nametag.CThought)
            self.nametagGroup.addNametag(self.chatNode)
            self.nametagGroup.setChat(TTLocalizer.PartyPlannerInstructions, CFSpeech)
            self.chatNP = parent.attachNewNode(self.chatNode.upcastToPandaNode())
            chatPos = self.gui.find('**/step_01_partymanPeteText_locator').getPos()
            self.chatNP.setPosHprScale(chatPos[0], 0, chatPos[2], 0, 0, 0, 0.08, 1, 0.08)
        return

    def clearNametag(self):
        if self.nametagGroup != None:
            self.nametagGroup.unmanage(base.marginManager)
            self.nametagGroup.removeNametag(self.nametagNode)
            self.nametagGroup.removeNametag(self.chatNode)
            self.nametagNP.removeNode()
            self.chatNP.removeNode()
            del self.nametagNP
            del self.chatNP
            del self.nametagNode
            del self.chatNode
            self.nametagGroup.setAvatar(NodePath())
            self.nametagGroup = None
        return

    def _createWelcomePage(self):
        self.nametagGroup = None
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerWelcomePage')
        self.welcomeTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerWelcomeTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.partyPlannerHead = ToonHead.ToonHead()
        partyPlannerStyle = base.cr.partyManager.getPartyPlannerStyle()
        self.partyPlannerHead.setupHead(partyPlannerStyle, forGui=True)
        self.partyPlannerHead.setPos(self.gui.find('**/step_01_partymanPete_locator').getPos())
        animal = partyPlannerStyle.getAnimal()
        if animal == 'cat' or animal == 'pig':
            headScale = 0.4
        elif animal == 'dog' or animal == 'bear':
            headScale = 0.45
        elif animal == 'rabbit':
            headScale = 0.35
        else:
            headScale = 0.3
        self.partyPlannerHead.setScale(headScale)
        self.partyPlannerHead.setH(180.0)
        self.partyPlannerHead.reparentTo(page)
        self.__createNametag(page)
        return page

    def _createDatePage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerDatePage')
        self.createDateTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerDateTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        pos = self.gui.find('**/step_06_sendInvitation_locator').getPos()
        self.makePartyNowButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/send_up'), self.gui.find('**/send_down'), self.gui.find('**/send_rollover')), text=TTLocalizer.PartyPlannerPartyNow, text_pos=(pos[0], pos[2]), text_scale=0.05, command=self.__doMakePartyNow)
        curServerDate = base.cr.toontownTimeManager.getCurServerDateTime()
        self.calendarGuiMonth = CalendarGuiMonth(page, curServerDate, scale=0.95, pos=(-0.05, 0.0, -0.33), dayClickCallback=self._dayClickCallback, onlyFutureDaysClickable=True)
        return page

    def __doMakePartyNow(self):
        self.request('Invitation')

    def _dayClickCallback(self, calendarGuiDay):
        self.selectedCalendarGuiDay = calendarGuiDay
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.makePartyNowButton.hide()
        self.nextButton.show()

    def alterPartyTime(self, year = None, month = None, day = None, hour = None, minute = None):
        self.partyTime = datetime(year=self.positiveTime('year', year), month=self.positiveTime('month', month), day=self.positiveTime('day', day), hour=self.positiveTime('hour', hour), minute=self.positiveTime('minute', minute), tzinfo=self.partyTime.tzinfo)

    def positiveTime(self, type, amount):
        if amount is None:
            return getattr(self.partyTime, type)
        if type == 'hour' or type == 'minute':
            if amount < 0:
                return self.timeTypeToMaxValue[type] + 1 + self.timeTypeToChangeAmount[type][1]
            elif amount > self.timeTypeToMaxValue[type]:
                return 0
        return amount

    def _createTimePage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerTimePage')
        self.createTimeTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.clockImage = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/toontownTime_background'))
        self.timePageToontownLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeToontown, pos=self.gui.find('**/step_03_toontown_locator').getPos(), scale=0.15, text_fg=(1.0, 0.0, 0.0, 1.0), text_font=ToontownGlobals.getSignFont())
        self.timePageTimeLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeTime, pos=self.gui.find('**/step_03_time_locator').getPos(), scale=0.15, text_fg=(1.0, 0.0, 0.0, 1.0), text_font=ToontownGlobals.getSignFont())
        self.timePageRecapLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeRecap, pos=self.gui.find('**/step_03_partyDateAndTime_locator').getPos(), scale=0.09)
        self.timePageRecapToontownTimeLabel1 = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeToontownTime, pos=self.gui.find('**/step_03_toontownTime_locator').getPos(), scale=0.06)
        self.timePageRecapToontownTimeLabel2 = DirectLabel(parent=page, relief=None, text='%s' % PartyUtils.formatDateTime(self.partyTime), pos=self.gui.find('**/step_03_toontownDateAndTime_loactor').getPos(), textMayChange=True, scale=0.06)
        self.timePageRecapLocalTimeLabel = DirectLabel(parent=page, relief=None, text='%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True)), pos=self.gui.find('**/step_03_localDateAndTime_loactor').getPos(), textMayChange=True, scale=0.06, text_fg=(1.0, 0.0, 0.0, 1.0))
        self.timeInputHourLabel, self.timeInputHourUpButton, self.timeInputHourDownButton = self.getTimeWidgets(page, 'hour')
        self.timeInputMinuteLabel, self.timeInputMinuteUpButton, self.timeInputMinuteDownButton = self.getTimeWidgets(page, 'minute')
        self.timeInputAmPmLabel, self.timeInputAmPmUpButton, self.timeInputAmPmDownButton = self.getTimeWidgets(page, 'ampm')
        self.timePagecolonLabel = DirectLabel(parent=page, relief=None, text=':', pos=self.gui.find('**/step_03_colon_locator').getPos(), scale=0.15)
        return page

    def getTimeWidgets(self, page, type):
        if type == 'ampm':
            data = self.getCurrentAmPm()
        else:
            data = getattr(self.partyTime, type)
            if data == 0 and type == 'minute':
                data = '00'
            else:
                if type == 'hour':
                    data = data % 12
                    if data == 0:
                        data = 12
                data = '%d' % data
        label = DirectLabel(parent=page, relief=None, text='%s' % data, textMayChange=True, pos=self.gui.find('**/step_03_%s_locator' % type).getPos(), scale=0.12)

        def changeValue(self, amount):
            if type == 'ampm':
                self.alterPartyTime(hour=(self.partyTime.hour + 12) % 24)
                newAmount = self.getCurrentAmPm()
                label['text'] = newAmount
            else:
                if type == 'hour':
                    newAmount = getattr(self.partyTime, type) + amount
                    newAmount = newAmount % 12
                    if self.timeInputAmPmLabel['text'] == TTLocalizer.PartyTimeFormatMeridiemPM:
                        newAmount = newAmount % 12 + 12
                    self.alterPartyTime(hour=newAmount)
                elif type == 'minute':
                    newAmount = getattr(self.partyTime, type) + amount
                    self.alterPartyTime(minute=newAmount)
                else:
                    PartyPlanner.notify.error('Invalid type for changeValue in PartyPlanner: %s' % type)
                newAmount = getattr(self.partyTime, type)
                if newAmount < 10 and type == 'minute':
                    label['text'] = '0%d' % newAmount
                else:
                    if type == 'hour':
                        newAmount = newAmount % 12
                        if newAmount == 0:
                            newAmount = 12
                    label['text'] = '%d' % newAmount
            self.timePageRecapToontownTimeLabel2['text'] = '%s' % PartyUtils.formatDateTime(self.partyTime)
            self.timePageRecapLocalTimeLabel['text'] = '%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True))

        upButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/%sButtonUp_up' % type), self.gui.find('**/%sButtonUp_down' % type), self.gui.find('**/%sButtonUp_rollover' % type)), command=changeValue, extraArgs=[self, self.timeTypeToChangeAmount[type][0]])
        downButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/%sButtonDown_up' % type), self.gui.find('**/%sButtonDown_down' % type), self.gui.find('**/%sButtonDown_rollover' % type)), command=changeValue, extraArgs=[self, self.timeTypeToChangeAmount[type][1]])
        return (label, upButton, downButton)

    def getCurrentAmPm(self):
        if self.partyTime.hour < 12:
            return TTLocalizer.PartyTimeFormatMeridiemAM
        else:
            return TTLocalizer.PartyTimeFormatMeridiemPM

    def _createGuestPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerGuestPage')
        self.guestTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerGuestTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.guestBackgroundLabel = DirectLabel(parent=page, relief=None, image=self.gui.find('**/guestListBackground_flat'), scale=(1.2, 1.0, 1.0))
        self.friendList = ScrolledFriendList(page, self.gui, makeItemsCheckBoxes=True)
        if len(base.localAvatar.friendsList) == 0:
            self.noFriends = True
        else:
            self.noFriends = False
            for friendPair in base.localAvatar.friendsList:
                self.friendList.addFriend(determineFriendName(friendPair), friendPair[0])

            self.friendList.scrollTo(0)
        pos = self.gui.find('**/step_04_partyWillBe_locator').getPos()
        self.publicPrivateLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPublicPrivateLabel, text_align=TextNode.ACenter, text_scale=0.065, pos=pos)
        self.publicDescriptionLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPublicDescription, text_align=TextNode.ACenter, text_scale=TTLocalizer.PPpbulicDescriptionLabel, pos=(pos[0] - 0.52, pos[1], pos[2]))
        self.publicDescriptionLabel.stash()
        self.privateDescriptionLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPrivateDescription, text_align=TextNode.ACenter, text_scale=TTLocalizer.PPprivateDescriptionLabel, pos=(pos[0] + 0.55, pos[1], pos[2]))
        self.privateDescriptionLabel.stash()
        pos = self.gui.find('**/step_04_public_locator').getPos()
        self.publicButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/publicButton_up'),
         self.gui.find('**/publicButton_down'),
         self.gui.find('**/publicButton_rollover'),
         self.gui.find('**/publicButton_inactive')), text=TTLocalizer.PartyPlannerPublic, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPpublicButton, command=self.__doTogglePublicPrivate)
        self.publicButton['state'] = DirectGuiGlobals.DISABLED
        self.publicButton.bind(DirectGuiGlobals.ENTER, self.__enterPublic)
        self.publicButton.bind(DirectGuiGlobals.EXIT, self.__exitPublic)
        pos = self.gui.find('**/step_04_private_locator').getPos()
        self.privateButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/privateButton_up'),
         self.gui.find('**/privateButton_down'),
         self.gui.find('**/privateButton_rollover'),
         self.gui.find('**/privateButton_inactive')), text=TTLocalizer.PartyPlannerPrivate, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPprivateButton, command=self.__doTogglePublicPrivate)
        self.privateButton.bind(DirectGuiGlobals.ENTER, self.__enterPrivate)
        self.privateButton.bind(DirectGuiGlobals.EXIT, self.__exitPrivate)
        self.checkAllButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/checkAllButton_up'), self.gui.find('**/checkAllButton_down'), self.gui.find('**/checkAllButton_rollover')), command=self.__doCheckAll)
        self.uncheckAllButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/uncheckAllButton_up'), self.gui.find('**/uncheckAllButton_down'), self.gui.find('**/uncheckAllButton_rollover')), command=self.__doUncheckAll)
        return page

    def __doCheckAll(self):
        for friendBox in self.friendList['items']:
            friendBox['indicatorValue'] = True

    def __doUncheckAll(self):
        for friendBox in self.friendList['items']:
            friendBox['indicatorValue'] = False

    def __enterPrivate(self, mouseEvent):
        self.privateDescriptionLabel.unstash()

    def __exitPrivate(self, mouseEvent):
        self.privateDescriptionLabel.stash()

    def __enterPublic(self, mouseEvent):
        self.publicDescriptionLabel.unstash()

    def __exitPublic(self, mouseEvent):
        self.publicDescriptionLabel.stash()

    def __doTogglePublicPrivate(self):
        if self.isPrivate:
            self.isPrivate = False
            self.privateButton['state'] = DirectGuiGlobals.NORMAL
            self.publicButton['state'] = DirectGuiGlobals.DISABLED
        else:
            self.isPrivate = True
            self.privateButton['state'] = DirectGuiGlobals.DISABLED
            self.publicButton['state'] = DirectGuiGlobals.NORMAL

    def _createPartyEditorPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerEditorPage')
        self.LayoutTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerEditorTitle, pos=self.gui.find('**/title_locator').getPos() + Point3(0.0, 0.0, 0.075), scale=self.titleScale)
        self.costLabel = DirectLabel(parent=page, pos=(-0.74, 0.0, 0.17), relief=None, text=TTLocalizer.PartyPlannerTotalCost % 0, text_align=TextNode.ACenter, scale=TTLocalizer.PPcostLabel, textMayChange=True)
        self.partyGridBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/partyGrid_flat'))
        self.partyGroundsLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPartyGrounds, text_font=ToontownGlobals.getSignFont(), text_fg=VBase4(1.0, 0.0, 0.0, 1.0), text_scale=TTLocalizer.PPpartyGroundsLabel, pos=self.gui.find('**/step_05_partyGrounds_text_locator').getPos(), scale=0.1)
        self.activityBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/activitiesDecorations_flat1'), pos=(0.0, 0.0, 0.04))
        pos = self.gui.find('**/step_05_instructions_locator').getPos()
        self.instructionLabel = DirectLabel(parent=page, relief=None, text=' ', text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPinstructionLabel, textMayChange=True, geom=self.gui.find('**/instructions_flat'))
        self.elementTitleLabel = DirectLabel(parent=page, relief=None, text=' ', pos=self.gui.find('**/step_05_activitiesName_text_locator').getPos() + Point3(0.0, 0.0, 0.04), text_scale=TTLocalizer.PPelementTitleLabel, textMayChange=True)
        self.elementPriceNode = TextNode('ElementPrice')
        self.elementPriceNode.setAlign(TextNode.ALeft)
        self.elementPriceNode.setTextColor(0.0, 0.0, 0.0, 1.0)
        self.elementPriceNode.setFont(ToontownGlobals.getToonFont())
        self.elementPrice = page.attachNewNode(self.elementPriceNode)
        self.elementPrice.setScale(TTLocalizer.PPelementPriceNode)
        self.elementPrice.setPos(self.gui.find('**/step_05_activityPrice_text_locator').getPos() + Point3(-0.02, 0.0, 0.04))
        self.elementDescriptionNode = TextNode('ElementDescription')
        self.elementDescriptionNode.setAlign(TextNode.ACenter)
        self.elementDescriptionNode.setWordwrap(8)
        self.elementDescriptionNode.setFont(ToontownGlobals.getToonFont())
        self.elementDescriptionNode.setTextColor(0.0, 0.0, 0.0, 1.0)
        self.elementDescription = page.attachNewNode(self.elementDescriptionNode)
        self.elementDescription.setScale(TTLocalizer.PPelementDescription)
        self.elementDescription.setPos(self.gui.find('**/step_05_activityDescription_text_locator').getPos() + Point3(0.0, 0.0, 0.04))
        self.totalMoney = base.localAvatar.getTotalMoney()
        catalogGui = loader.loadModel('phase_5.5/models/gui/catalog_gui')
        self.beanBank = DirectLabel(parent=page, relief=None, text=str(self.totalMoney), text_align=TextNode.ARight, text_scale=0.075, text_fg=(0.95, 0.95, 0, 1), text_shadow=(0, 0, 0, 1), text_pos=(0.495, -0.53), text_font=ToontownGlobals.getSignFont(), textMayChange=True, image=catalogGui.find('**/bean_bank'), image_scale=(0.65, 0.65, 0.65), scale=0.9, pos=(-0.75, 0.0, 0.6))
        catalogGui.removeNode()
        del catalogGui
        self.accept(localAvatar.uniqueName('moneyChange'), self.__moneyChange)
        self.accept(localAvatar.uniqueName('bankMoneyChange'), self.__moneyChange)
        self.partyEditor = PartyEditor(self, page)
        self.partyEditor.request('Hidden')
        pos = self.gui.find('**/step_05_add_text_locator').getPos()
        self.elementBuyButton = DirectButton(parent=page, relief=None, text=TTLocalizer.PartyPlannerBuy, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPelementBuyButton, geom=(self.gui.find('**/add_up'), self.gui.find('**/add_down'), self.gui.find('**/add_rollover')), geom3_color=VBase4(0.5, 0.5, 0.5, 1.0), textMayChange=True, pos=(0.0, 0.0, 0.04), command=self.partyEditor.buyCurrentElement)
        self.okWithPartyGroundsLayoutEvent = 'okWithPartyGroundsLayoutEvent'
        self.accept(self.okWithPartyGroundsLayoutEvent, self.okWithPartyGroundsLayout)
        self.okWithGroundsGui = TTDialog.TTGlobalDialog(dialogName=self.uniqueName('PartyEditorOkGui'), doneEvent=self.okWithPartyGroundsLayoutEvent, message=TTLocalizer.PartyPlannerOkWithGroundsLayout, style=TTDialog.YesNo, okButtonText=OTPLocalizer.DialogYes, cancelButtonText=OTPLocalizer.DialogNo)
        self.okWithGroundsGui.doneStatus = ''
        self.okWithGroundsGui.hide()
        return page

    def okWithPartyGroundsLayout(self):
        self.okWithGroundsGui.hide()
        if self.okWithGroundsGui.doneStatus == 'ok':
            self.__nextItem()

    def setNextButtonState(self, enabled):
        if enabled:
            self.nextButton['state'] = DirectGuiGlobals.NORMAL
            self.nextButton.show()
        else:
            self.nextButton['state'] = DirectGuiGlobals.DISABLED
            self.nextButton.hide()

    def _createInvitationPage(self):
        self.__handleHolidays()
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerInvitationPage')
        self.invitationTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerConfirmTitle, textMayChange=True, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.invitationBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/invitationBackground'))
        self.inviteVisual = InviteVisual(page)
        self.selectedInviteThemeLabel = DirectLabel(parent=page, relief=None, pos=self.gui.find('**/step_06_theme_locator').getPos(), text='', text_scale=0.06, textMayChange=True)
        self.nextThemeButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/topNext_button/nextButton_up'), self.gui.find('**/topNext_button/nextButton_down'), self.gui.find('**/topNext_button/nextButton_rollover')), command=self.__nextTheme)
        self.prevThemeButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/topPrevious_button/previousButton_up'), self.gui.find('**/topPrevious_button/previousButton_down'), self.gui.find('**/topPrevious_button/previousButton_rollover')), command=self.__prevTheme)
        pos = self.gui.find('**/step_06_sendInvitation_locator').getPos()
        self.inviteButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/send_up'), self.gui.find('**/send_down'), self.gui.find('**/send_rollover')), text=TTLocalizer.PartyPlannerInviteButton, textMayChange=True, text_scale=0.05, text_pos=(pos[0], pos[2]), command=self.__handleComplete)
        return page

    def __handleHolidays(self):
        self.inviteThemes = list(range(len(PartyGlobals.InviteTheme)))
        if hasattr(base.cr, 'newsManager') and base.cr.newsManager:
            holidayIds = base.cr.newsManager.getHolidayIdList()
            if ToontownGlobals.VALENTINES_DAY not in holidayIds:
                self.inviteThemes.remove(PartyGlobals.InviteTheme.Valentoons)
            if ToontownGlobals.VICTORY_PARTY_HOLIDAY not in holidayIds:
                self.inviteThemes.remove(PartyGlobals.InviteTheme.VictoryParty)
            if ToontownGlobals.WINTER_DECORATIONS not in holidayIds and ToontownGlobals.WACKY_WINTER_DECORATIONS not in holidayIds:
                self.inviteThemes.remove(PartyGlobals.InviteTheme.Winter)

    def _createFarewellPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerFarewellPage')
        self.confirmTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerConfirmationAllOkTitle, textMayChange=True, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        pos = self.gui.find('**/step_07_close_text_locator').getPos()
        self.closePlannerButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/close_up'), self.gui.find('**/close_down'), self.gui.find('**/close_rollover')), text=TTLocalizer.PartyPlannerClosePlanner, text_scale=0.055, text_pos=(pos[0], pos[2]), command=self.__acceptExit)
        return page

    def close(self):
        self.ignore('addPartyResponseReceived')
        self.ignore(localAvatar.uniqueName('moneyChange'))
        self.ignore(localAvatar.uniqueName('bankMoneyChange'))
        self.timeInputHourUpButton.destroy()
        self.timeInputHourDownButton.destroy()
        self.timeInputMinuteUpButton.destroy()
        self.timeInputMinuteDownButton.destroy()
        self.timeInputAmPmUpButton.destroy()
        self.timeInputAmPmDownButton.destroy()
        self.privateButton.destroy()
        self.publicButton.destroy()
        self.makePartyNowButton.destroy()
        self.checkAllButton.destroy()
        self.uncheckAllButton.destroy()
        self.elementBuyButton.destroy()
        self.nextThemeButton.destroy()
        self.prevThemeButton.destroy()
        self.inviteButton.destroy()
        self.closePlannerButton.destroy()
        self.ignore(self.okWithPartyGroundsLayoutEvent)
        if hasattr(self, 'okWithGroundsGui'):
            self.okWithGroundsGui.cleanup()
            del self.okWithGroundsGui
        if hasattr(self, 'frame') and not self.frame.isEmpty():
            messenger.send(self.doneEvent)
            self.hide()
            self.cleanup()
            self.friendList.removeAndDestroyAllItems()
            self.friendList.destroy()
            self.calendarGuiMonth.destroy()
            self.frame.destroy()
        self.partyPlannerHead.delete()
        self.partyPlannerHead.removeNode()
        self.clearNametag()
        self.partyEditor.request('Cleanup')
        self.partyEditor = None
        self.destroy()
        del self
        return

    def __handleComplete(self):
        self.inviteButton['state'] = DirectGuiGlobals.DISABLED
        self.prevButton['state'] = DirectGuiGlobals.DISABLED
        endTime = self.partyTime + self.partyDuration
        hostId = base.localAvatar.doId
        self.partyActivities = self.partyEditor.partyEditorGrid.getActivitiesOnGrid()
        decorations = self.partyEditor.partyEditorGrid.getDecorationsOnGrid()
        invitees = self.getInvitees()
        self.accept('addPartyResponseReceived', self.processAddPartyResponse)
        base.cr.partyManager.sendAddParty(hostId, self.partyTime.strftime('%Y-%m-%d %H:%M:%S'), endTime.strftime('%Y-%m-%d %H:%M:%S'), self.isPrivate, self.currentInvitationTheme, self.partyActivities, decorations, invitees)

    def getInvitees(self):
        invitees = []
        for friendBox in self.friendList['items']:
            if friendBox['indicatorValue']:
                invitees.append(friendBox.getPythonTag('id'))

        return invitees

    def processAddPartyResponse(self, hostId, errorCode):
        PartyPlanner.notify.debug('processAddPartyResponse : hostId=%d errorCode=%s' % (hostId, PartyGlobals.AddPartyErrorCode.getString(errorCode)))
        goingBackAllowed = False
        if errorCode == PartyGlobals.AddPartyErrorCode.AllOk:
            goingBackAllowed = False
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationAllOkTitle
            if self.noFriends or len(self.getInvitees()) == 0:
                confirmRecapText = TTLocalizer.PartyPlannerConfirmationAllOkTextNoFriends
            else:
                confirmRecapText = TTLocalizer.PartyPlannerConfirmationAllOkText
        elif errorCode == PartyGlobals.AddPartyErrorCode.ValidationError:
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationValidationErrorText
        elif errorCode == PartyGlobals.AddPartyErrorCode.DatabaseError:
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationDatabaseErrorText
        elif errorCode == PartyGlobals.AddPartyErrorCode.TooManyHostedParties:
            goingBackAllowed = False
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationTooManyText
        self.nametagGroup.setChat(confirmRecapText, CFSpeech)
        self.request('Farewell', goingBackAllowed)

    def __acceptExit(self):
        PartyPlanner.notify.debug('__acceptExit')
        if hasattr(self, 'frame'):
            self.hide()
            messenger.send(self.doneEvent)

    def __nextItem(self):
        messenger.send('wakeup')
        if self.state == 'PartyEditor' and self.okWithGroundsGui.doneStatus != 'ok':
            self.okWithGroundsGui.show()
            return
        if self.state == 'PartyEditor' and self.noFriends:
            self.request('Date')
            self.selectedCalendarGuiDay = None
            self.calendarGuiMonth.clearSelectedDay()
            return
        if self.state == 'Guests':
            self.selectedCalendarGuiDay = None
            self.calendarGuiMonth.clearSelectedDay()
        if self.state == 'Time':
            if self.partyTime < base.cr.toontownTimeManager.getCurServerDateTime():
                self.okChooseFutureTimeEvent = 'okChooseFutureTimeEvent'
                self.acceptOnce(self.okChooseFutureTimeEvent, self.okChooseFutureTime)
                self.chooseFutureTimeDialog = TTDialog.TTGlobalDialog(dialogName=self.uniqueName('chooseFutureTimeDialog'), doneEvent=self.okChooseFutureTimeEvent, message=TTLocalizer.PartyPlannerChooseFutureTime, style=TTDialog.Acknowledge)
                self.chooseFutureTimeDialog.show()
                return
        self.requestNext()
        return

    def okChooseFutureTime(self):
        if hasattr(self, 'chooseFutureTimeDialog'):
            self.chooseFutureTimeDialog.cleanup()
            del self.chooseFutureTimeDialog
        if hasattr(self, 'okChooseFutureTimeEvent'):
            self.ignore(self.okChooseFutureTimeEvent)

    def __prevItem(self):
        messenger.send('wakeup')
        if self.state == 'Date' and self.noFriends:
            self.request('PartyEditor')
            return
        if self.state == 'Invitation' and self.selectedCalendarGuiDay is None:
            self.request('Guests')
            return
        self.requestPrev()
        return

    def __moneyChange(self, newMoney):
        if hasattr(self, 'totalMoney'):
            self.totalMoney = base.localAvatar.getTotalMoney()
        if hasattr(self, 'beanBank'):
            self.beanBank['text'] = str(int(self.totalMoney))
Пример #3
0
class RepairSawingGame(RepairMincroGame):
    sawSounds = None
    boardComplet = None
    boardDestroyed = None

    def __init__(self, repairGame):
        self.config = RepairGlobals.Sawing
        notify = DirectNotifyGlobal.directNotify.newCategory(
            'RepairSawingGame')
        RepairMincroGame.__init__(self, repairGame, 'sawing',
                                  PLocalizer.Minigame_Repair_Sawing_Start)

    def _initVars(self):
        RepairMincroGame._initVars(self)
        self.boardsPool = {}
        self.currentBoard = None
        self.currentBoardIndex = 0
        self.onDeckBoard = None
        self.onDeckBoardIndex = 0
        self.totalScore = 0.0
        self.hitZone1Penalty = False
        self.hitZone2Penalty = False
        self.hitBoardPenalty = False
        self.moveDiffForSound = 0.0
        self.startPositions = (Point3(0.0, 0.0, 0.0), )
        self.currentStartIndex = 0
        self.lastMousePos = None
        self.board_left = None
        self.board_right = None
        self.cut = None
        self.zone1_right = None
        self.zone1_left = None
        self.zone2_right = None
        self.zone2_left = None
        self.piece1 = None
        self.piece2 = None
        self.lastHitIndex = -1
        self.sawWaypoints = []

    def _initAudio(self):
        RepairMincroGame._initAudio(self)
        if not self.sawSounds:
            RepairSawingGame.sawSounds = (
                loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT01),
                loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT02),
                loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT03),
                loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT04))
            RepairSawingGame.boardComplete = loadSfx(
                SoundGlobals.SFX_MINIGAME_REPAIR_SAW_COMPLETE)
            RepairSawingGame.boardDestroyed = loadSfx(
                SoundGlobals.SFX_MINIGAME_REPAIR_SAW_FAIL)

    def _initVisuals(self):
        RepairMincroGame._initVisuals(self)
        self.setBin('fixed', 36)
        self.model = loader.loadModel('models/gui/pir_m_gui_srp_sawing_main')
        sawModel = self.model.find('**/saw')
        sawModel.setR(193)
        sawModel.setPos(0.90000000000000002, 0.0, -0.16500000000000001)
        sawModel.setBin('gui-popup', 0)
        self.sawButton = RepairSaw(
            parent=self,
            clickDownCommand=self.sawAttachedToMouse,
            clickUpCommand=self.sawRemovedFromMouse,
            geom=sawModel,
            text_pos=(0.20000000000000001, -0.29999999999999999),
            text_fg=(1, 0, 0, 1),
            scale=(0.29999999999999999, 0.29999999999999999,
                   0.29999999999999999),
            relief=None,
            pressEffect=0,
            frameSize=(-0.050000000000000003, 1.05, -0.29999999999999999,
                       0.050000000000000003),
            rolloverSound=None,
            clickSound=None)
        self.sawingLine = RepairSawingLine(self,
                                           self.config.sawlineLineThickness,
                                           self.config.sawlineColor,
                                           self.config.sawlineLinespawnDist)
        self.progressDescriptionLabel = DirectLabel(
            text=PLocalizer.Minigame_Repair_Sawing_Description,
            text_fg=(1.0, 1.0, 1.0, 1.0),
            text_pos=(0.0, 0.0),
            text_shadow=(0.0, 0.0, 0.0, 1.0),
            text_font=PiratesGlobals.getPirateFont(),
            text_align=TextNode.ARight,
            relief=None,
            scale=(0.080000000000000002, 0.080000000000000002,
                   0.080000000000000002),
            pos=(-0.20000000000000001, 0.0, 0.5),
            parent=self)
        self.progressLabel = DirectLabel(
            text=PLocalizer.Minigame_Repair_Sawing_Thresholds[3],
            text_fg=(1.0, 1.0, 1.0, 1.0),
            text_pos=(0.0, 0.0),
            text_shadow=(0.0, 0.0, 0.0, 1.0),
            text_font=PiratesGlobals.getPirateFont(),
            text_align=TextNode.ALeft,
            relief=None,
            scale=(0.080000000000000002, 0.080000000000000002,
                   0.080000000000000002),
            pos=(-0.17999999999999999, 0.0, 0.5),
            parent=self)
        self.boardDestroyedLabel = DirectLabel(
            text=PLocalizer.Minigame_Repair_Sawing_Board_Destroyed,
            text_fg=(1.0, 0.0, 0.0, 1.0),
            text_pos=(0.0, 0.0),
            text_font=PiratesGlobals.getPirateFont(),
            text_shadow=(0.0, 0.0, 0.0, 1.0),
            relief=None,
            scale=(0.10000000000000001, 0.10000000000000001,
                   0.10000000000000001),
            pos=(0.0, 0.0, 0.10000000000000001),
            parent=self)
        self.boardDestroyedLabel.setBin('fixed', 38)
        self.boardDestroyedLabel.stash()

    def _initIntervals(self):
        RepairMincroGame._initIntervals(self)
        self.newBoardSequence = Sequence(
            name='RepairSawingGame.newBoardSequence')
        self.splitBoardSequence = Sequence(
            name='RepairSawGame.splitBoardSequence')
        self.dropBoardSequence = Sequence(
            name='RepairSawGame.dropBoardSequence')

    def getNewBoard(self, boardIndex):
        board = self.model.find('**/wood%i' % boardIndex).copyTo(
            NodePath('board%i' % len(self.boardsPool)))
        board.reparentTo(self)
        piece1 = board.find('**/piece_1')
        piece1.setPythonTag('piece_1', self.piece1)
        piece1.setY(self.config.boardYDist)
        piece2 = board.find('**/piece_2')
        piece2.setPythonTag('piece_2', self.piece2)
        piece2.setY(self.config.boardYDist)
        pieceCut = board.find('**/piece_cut')
        pieceCut.setPythonTag('cut', self.cut)
        pieceCut.setColor(self.config.cutColor)
        pieceCut.setY(self.config.boardYDist)
        board_left = piece1.find('**/board')
        board_left.setPythonTag('left', self.board_left)
        board_right = piece2.find('**/board')
        board_right.setPythonTag('right', self.board_right)
        zone1_right = piece2.find('**/zone_1')
        zone1_right.setPythonTag('zone1_right', self.zone1_right)
        zone1_right.setColor(self.config.zone1Color)
        zone1_left = piece1.find('**/zone_1')
        zone1_left.setPythonTag('zone1_left', self.zone1_left)
        zone1_left.setColor(self.config.zone1Color)
        zone2_right = piece2.find('**/zone_2')
        zone2_right.setPythonTag('zone2_right', self.zone2_right)
        zone2_right.setColor(self.config.zone2Color)
        zone2_left = piece1.find('**/zone_2')
        zone2_left.setPythonTag('zone2_left', self.zone2_left)
        zone2_left.setColor(self.config.zone2Color)
        board.stash()
        return board

    def reset(self):
        for key in self.boardsPool.keys():
            board = self.boardsPool[key]
            board.removeNode()

        self.boardsPool.clear()
        if self.currentBoard:
            self.currentBoard.removeNode()
            self.currentBoard = None

        if self.onDeckBoard:
            self.onDeckBoard.removeNode()
            self.onDeckBoard = None

        for boardIndex in self.currentDifficultySet:
            boardIndex -= 1
            if 'copy1_%s' % boardIndex not in self.boardsPool:
                board = self.getNewBoard(boardIndex)
                self.boardsPool['copy1_%s' % boardIndex] = board

            if 'copy2_%s' % boardIndex not in self.boardsPool:
                board = self.getNewBoard(boardIndex)
                self.boardsPool['copy2_%s' % boardIndex] = board
                continue

        self.currentBoardIndex = 0
        self.currentBoard = None
        self.moveNewBoardOnDeck()
        self.onDeckBoard.unstash()
        self.totalScore = 0
        self.startPositions = (Point3(0.0, 0.0, 0.0), )
        self.sawButton.stash()
        self.sawButton.reparentTo(self)
        self.lastHitIndex = -1
        self.moveDiffForSound = 0.0
        RepairMincroGame.reset(self)
        self.repairGame.gui.setTutorial(self.name)
        self.repairGame.gui.setTitle(self.name)

    def destroy(self):
        RepairMincroGame.destroy(self)
        taskMgr.remove('SawingGame.updateSawTask')
        self.sawButton.destroy()
        self.sawButton.removeNode()
        del self.sawButton
        if self.currentBoard:
            self.currentBoard.removeNode()
            self.currentBoard = None

        if self.onDeckBoard:
            self.onDeckBoard.removeNode()
            self.onDeckBoard = None

        self.sawingLine = None
        self.progressDescriptionLabel.destroy()
        self.progressDescriptionLabel = None
        self.progressLabel.destroy()
        self.progressLabel = None
        self.boardDestroyedLabel.destroy()
        self.boardDestroyedLabel = None
        for key in self.boardsPool.keys():
            board = self.boardsPool[key]
            if not board.isEmpty():
                board.removeNode()
                continue

        self.boardsPool.clear()
        self.newBoardSequence.clearToInitial()
        del self.newBoardSequence
        self.splitBoardSequence.clearToInitial()
        del self.splitBoardSequence
        self.dropBoardSequence.clearToInitial()
        del self.dropBoardSequence

    def setDifficulty(self, difficulty):
        RepairMincroGame.setDifficulty(self, difficulty)
        percent = difficulty / self.repairGame.difficultyMax
        difIndex = int(
            math.floor(percent * (len(self.config.difficultySets) - 1)))
        self.currentDifficultySet = self.config.difficultySets[difIndex]

    def splitBoard(self):
        self.sawingLine.reset()
        board = self.currentBoard
        boardIndex = self.currentBoardIndex
        if self.hitZone2Penalty:
            boardSplitAnim = Parallel(
                LerpPosInterval(self.board_left,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(-2.0, 0.0, 0.0)),
                LerpPosInterval(self.board_right,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(2.0, 0.0, 0.0)),
                LerpFunc(self.zone2_left.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0),
                LerpFunc(self.zone2_right.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0),
                LerpFunc(self.zone1_left.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0),
                LerpFunc(self.zone1_right.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0),
                LerpFunc(self.cut.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0))
        elif self.hitZone1Penalty:
            boardSplitAnim = Parallel(
                LerpPosInterval(self.board_left,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(-2.0, 0.0, 0.0)),
                LerpPosInterval(self.board_right,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(2.0, 0.0, 0.0)),
                LerpPosInterval(self.zone2_left,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(-2.0, 0.0, 0.0)),
                LerpPosInterval(self.zone2_right,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(2.0, 0.0, 0.0)),
                LerpFunc(self.zone1_left.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0),
                LerpFunc(self.zone1_right.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0),
                LerpFunc(self.cut.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0))
        else:
            boardSplitAnim = Parallel(
                LerpPosInterval(self.piece1,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(-2.0, self.config.boardYDist, 0.0)),
                LerpPosInterval(self.piece2,
                                duration=self.config.splitBoardAnimTime,
                                pos=Point3(2.0, self.config.boardYDist, 0.0)),
                LerpFunc(self.cut.setSa,
                         duration=self.config.splitBoardAnimTime / 2.0,
                         fromData=1.0,
                         toData=0.0))
        self.splitBoardSequence = Sequence(
            Func(self.updateScore),
            Func(self.boardComplete.play),
            boardSplitAnim,
            Func(board.stash),
            Func(self.piece1.setPos, self.piece1.getPos()),
            Func(self.piece2.setPos, self.piece2.getPos()),
            Func(self.board_right.setPos, self.board_right.getPos()),
            Func(self.board_left.setPos, self.board_left.getPos()),
            Func(self.zone2_right.setPos, self.zone2_right.getPos()),
            Func(self.zone2_left.setPos, self.zone2_left.getPos()),
            Func(self.zone1_right.setPos, self.zone1_right.getPos()),
            Func(self.zone1_left.setPos, self.zone1_left.getPos()),
            Func(self.cut.setSa, 1.0),
            Func(self.zone1_right.setSa, 1.0),
            Func(self.zone1_left.setSa, 1.0),
            Func(self.zone2_right.setSa, 1.0),
            Func(self.zone2_left.setSa, 1.0),
            Func(self.board_right.setSa, 1.0),
            Func(self.board_left.setSa, 1.0),
            Func(self.loadNewBoard),
            Func(self.addBoardBackToPool, board, boardIndex),
            name='RepairSawGame.splitBoardSequence')
        self.splitBoardSequence.start()

    def dropBoard(self):
        board = self.currentBoard
        boardIndex = self.currentBoardIndex
        self.dropBoardSequence = Sequence(
            Parallel(
                Sequence(Func(self.boardDestroyedLabel.unstash), Wait(1.5),
                         Func(self.boardDestroyedLabel.stash)),
                Sequence(
                    Wait(0.5), Func(self.boardDestroyed.play),
                    Func(self.sawingLine.reset),
                    LerpPosInterval(board,
                                    duration=self.config.splitBoardAnimTime,
                                    pos=Point3(0.0, 0.0, -2.0)),
                    Func(board.stash), Wait(0.5), Func(self.loadNewBoard),
                    Func(self.addBoardBackToPool, board, boardIndex))),
            name='RepairSawGame.dropBoardSequence')
        self.dropBoardSequence.start()

    def addBoardBackToPool(self, board, boardIndex):
        if 'copy1_%s' % boardIndex not in self.boardsPool:
            self.boardsPool['copy1_%s' % boardIndex] = board
        elif 'copy2_%s' % boardIndex not in self.boardsPool:
            self.boardsPool['copy2_%s' % boardIndex] = board
        else:
            self.notify.error(
                'Two copies of board type %i already in the boardsPool!' %
                boardIndex)

    def updateScoreText(self):
        self.progressLabel.unstash()
        self.progressDescriptionLabel.unstash()
        if self.hitBoardPenalty:
            self.progressLabel[
                'text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[0]
            self.progressLabel['text_fg'] = Vec4(1.0, 0.0, 0.0, 1.0)
            self.progressLabel.setText()
        elif self.hitZone2Penalty:
            self.progressLabel[
                'text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[1]
            self.progressLabel['text_fg'] = Vec4(1.0, 0.5, 0.0, 1.0)
            self.progressLabel.setText()
        elif self.hitZone1Penalty:
            self.progressLabel[
                'text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[2]
            self.progressLabel['text_fg'] = Vec4(1.0, 1.0, 0.0, 1.0)
            self.progressLabel.setText()
        else:
            self.progressLabel[
                'text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[3]
            self.progressLabel['text_fg'] = Vec4(0.0, 1.0, 0.0, 1.0)
            self.progressLabel.setText()

    def moveNewBoardOnDeck(self):
        boardIndex = random.randint(0, len(self.currentDifficultySet) - 1)
        boardType = self.currentDifficultySet[boardIndex]
        boardType -= 1
        if 'copy1_%s' % boardType in self.boardsPool:
            self.onDeckBoard = self.boardsPool['copy1_%s' % boardType]
            del self.boardsPool['copy1_%s' % boardType]
        elif 'copy2_%s' % boardType in self.boardsPool:
            self.onDeckBoard = self.boardsPool['copy2_%s' % boardType]
            del self.boardsPool['copy2_%s' % boardType]
        else:
            self.notify.error('No copies of board type %i in the boardsPool!' %
                              boardType)
        self.onDeckBoardIndex = boardType
        self.onDeckBoard.setScale(0.25)
        self.onDeckBoard.setPos(0.5, -2.0, 0.56000000000000005)
        self.onDeckBoard.unstash()

    def loadNewBoard(self):
        self.progressLabel.stash()
        self.progressDescriptionLabel.stash()
        if self.totalScore >= self.config.totalPoints:
            if self.onDeckBoard:
                self.onDeckBoard.stash()

            self.progressDescriptionLabel.stash()
            taskMgr.remove('SawingGame.updateSawTask')
            self.request('Outro')
            return None

        self.currentBoard = self.onDeckBoard
        self.currentBoardIndex = self.onDeckBoardIndex
        self.piece1 = self.currentBoard.find('**/piece_1')
        self.piece1.setTransparency(1)
        self.piece2 = self.currentBoard.find('**/piece_2')
        self.piece2.setTransparency(1)
        self.cut = self.currentBoard.find('**/piece_cut')
        self.cut.setColor(self.config.cutColor)
        self.cut.setTransparency(1)
        self.board_left = self.piece1.find('**/board')
        self.board_left.setTransparency(1)
        self.zone1_left = self.piece1.find('**/zone_1')
        self.zone1_left.setTransparency(1)
        self.zone2_left = self.piece1.find('**/zone_2')
        self.zone2_left.setTransparency(1)
        self.board_right = self.piece2.find('**/board')
        self.board_right.setTransparency(1)
        self.zone1_right = self.piece2.find('**/zone_1')
        self.zone1_right.setTransparency(1)
        self.zone2_right = self.piece2.find('**/zone_2')
        self.zone2_right.setTransparency(1)
        self.board_left.setCollideMask(SAW_COLLIDE_MASK)
        self.board_right.setCollideMask(SAW_COLLIDE_MASK)
        self.cut.setCollideMask(SAW_COLLIDE_MASK)
        self.zone1_right.setCollideMask(SAW_COLLIDE_MASK)
        self.zone1_left.setCollideMask(SAW_COLLIDE_MASK)
        self.zone2_right.setCollideMask(SAW_COLLIDE_MASK)
        self.zone2_left.setCollideMask(SAW_COLLIDE_MASK)
        self.startPositions = (
            self.currentBoard.find('**/locator_start_0').getPos() +
            Point3(*self.config.activeBoardPosition),
            self.currentBoard.find('**/locator_start_1').getPos() +
            Point3(*self.config.activeBoardPosition))
        self.currentStartIndex = 0
        for waypoint in self.sawWaypoints:
            waypoint.removeNode()

        self.sawWaypoints = []
        locator = self.currentBoard.find('**/locator_0')
        index = 0
        while not locator.isEmpty():
            self.sawWaypoints.append(
                SawWaypoint(index, self.currentBoard, locator.getPos()))
            locator = self.currentBoard.find('**/locator_%i' % (index + 1))
            index += 1
        self.sawButton.deactivate()
        self.sawButton.setPos(self.startPositions[self.currentStartIndex])
        self.hitBoardPenalty = False
        self.hitZone1Penalty = False
        self.hitZone2Penalty = False
        self.lastMousePos = None
        self.moveDiffForSound = self.config.playSawingSoundDelta + 0.10000000000000001
        self.newBoardSequence = Sequence(
            Parallel(
                self.currentBoard.posInterval(
                    self.config.newBoardAnimTime,
                    Point3(*self.config.activeBoardPosition)),
                self.currentBoard.scaleInterval(self.config.newBoardAnimTime,
                                                1.0)),
            name='RepairSawingGame.newBoardSequence')
        if self.state in ['Game']:
            self.newBoardSequence.append(Func(self.sawButton.activate))

        self.newBoardSequence.append(Wait(0.5))
        self.newBoardSequence.append(Func(self.moveNewBoardOnDeck))
        self.newBoardSequence.start()

    def updateSawTask(self, task):
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            relative = Point3(mpos.getX(), 0.0, mpos.getY())
            relative = self.getRelativePoint(render2d, relative)
            moveDiff = 0.0
            if self.lastMousePos != None:
                moveDiff = (relative - self.lastMousePos).length()

            pickedObjects = self.repairGame.mousePicker.getCollisions(
                self.currentBoard, useIntoNodePaths=True)
            self.updateWaypoints()
            if len(pickedObjects) > 0:
                self.moveDiffForSound += moveDiff
                if self.moveDiffForSound > self.config.playSawingSoundDelta:
                    sawSoundPlaying = False
                    for sound in self.sawSounds:
                        if sound.status() == 2:
                            sawSoundPlaying = True
                            break
                            continue

                    if sawSoundPlaying == False:
                        sound = random.choice(self.sawSounds)
                        sound.play()
                        self.moveDiffForSound = 0.0

                if self.board_right in pickedObjects or self.board_left in pickedObjects:
                    for waypoint in self.sawWaypoints:
                        waypoint.hit = False

                    self.hitBoardPenalty = True
                    self.dropBoard()
                    self.sawButton.deactivate()
                elif self.cut in pickedObjects:
                    self.updateWaypoints()
                elif self.zone1_right in pickedObjects or self.zone1_left in pickedObjects:
                    self.updateWaypoints()
                    if self.hitZone1Penalty == False:
                        self.hitZone1Penalty = True

                elif self.zone2_right in pickedObjects or self.zone2_left in pickedObjects:
                    self.updateWaypoints()
                    if self.hitZone2Penalty == False:
                        self.hitZone2Penalty = True

                self.updateScoreText()
            else:
                boardComplete = True
                for waypoint in self.sawWaypoints:
                    if not waypoint.hit:
                        boardComplete = False
                        break
                        continue

                if boardComplete:
                    self.splitBoard()
                    self.sawButton.deactivate()

            self.lastMousePos = self.sawButton.getPos()

        return Task.cont

    def updateScore(self):
        if not self.hitBoardPenalty:
            currBoardScore = self.config.pointsPerBoard
            if not self.hitZone1Penalty:
                pass
            currBoardScore -= self.config.pointsLostForZone1 * self.hitZone2Penalty
            currBoardScore -= self.config.pointsLostForZone2 * self.hitZone2Penalty
            rating = 4 - 1 * self.hitZone2Penalty - 3 * self.hitZone1Penalty
            self.totalScore += currBoardScore
            self.totalScore = min(self.totalScore, self.config.totalPoints)
            percent = int((self.totalScore / self.config.totalPoints) * 100.0)
            self.repairGame.d_reportMincroGameProgress(percent, rating)

    def resetWaypoints(self):
        for waypoint in self.sawWaypoints:
            waypoint.hit = False

    def updateWaypoints(self):
        waypointList = self.getHitWaypoints()
        for waypointIndex in waypointList:
            self.lastHitIndex = waypointIndex
            self.sawWaypoints[waypointIndex].hit = True
            if waypointIndex == 0 and not (self.sawWaypoints[-1].hit):
                self.currentStartIndex = 0
                continue
            if waypointIndex == len(
                    self.sawWaypoints) - 1 and not (self.sawWaypoints[0].hit):
                self.currentStartIndex = 1
                continue

    def getHitWaypoints(self):
        waypointsHit = []
        testDelta = self.config.testWaypointDelta
        for i in range(len(self.sawWaypoints)):
            waypointPos = self.sawWaypoints[i].getPos(self)
            closestDistance = 9999
            if self.lastMousePos != None:
                currMousePos = self.sawButton.getPos()
                lastMousePos = self.lastMousePos
                totalLength = (currMousePos - lastMousePos).length()
                testLength = testDelta
                while testLength < totalLength:
                    testPos = (currMousePos - lastMousePos) * (
                        testLength / totalLength) + lastMousePos
                    self.updateSawLine(testPos)
                    testDistance = (testPos - waypointPos).length()
                    closestDistance = min(testDistance, closestDistance)
                    testLength += testDelta

            self.updateSawLine(self.sawButton.getPos())
            testDistance = (self.sawButton.getPos() - waypointPos).length()
            closestDistance = min(testDistance, closestDistance)
            if closestDistance < self.config.waypointRange[
                    self.currentBoardIndex]:
                waypointsHit.append(i)
                continue

        return waypointsHit

    def updateSawLine(self, pos):
        if pos.getX() < -0.63300000000000001 or pos.getX(
        ) > 0.63300000000000001:
            self.sawingLine.reset()
            return None

        if pos.getZ() < -0.183 or pos.getZ() > 0.375:
            self.sawingLine.reset()
            return None

        self.sawingLine.update(pos)

    def getClosestPosition(self, positions):
        closestIndex = -1
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            relative = Point3(mpos.getX(), 0.0, mpos.getY())
            relative = self.getRelativePoint(base.a2dBackground, relative)
            bestDistance = 99999.0
            for i in range(len(positions)):
                dX = relative.getX() - positions[i].getX()
                dZ = relative.getZ() - positions[i].getZ()
                newDistance = dX * dX + dZ * dZ
                if newDistance < bestDistance:
                    bestDistance = newDistance
                    closestIndex = i
                    continue

        return closestIndex

    def sawAttachedToMouse(self):
        self.lastHitIndex = -1
        if not taskMgr.hasTaskNamed('SawingGame.updateSawTask'):
            taskMgr.add(self.updateSawTask,
                        'SawingGame.updateSawTask',
                        priority=2)

    def sawRemovedFromMouse(self):
        if not self.sawButton.isStashed():
            self.sawButton.setPos(self.startPositions[self.currentStartIndex])
            self.lastHitIndex = -1
            self.resetWaypoints()
            self.lastMousePos = None
            self.progressLabel.stash()
            self.progressDescriptionLabel.stash()
            self.sawingLine.reset()
            self.hitBoardPenalty = False
            self.hitZone1Penalty = False
            self.hitZone2Penalty = False

        taskMgr.remove('SawingGame.updateSawTask')

    def enterIntro(self):
        RepairMincroGame.enterIntro(self)
        self.loadNewBoard()

    def enterGame(self):
        RepairMincroGame.enterGame(self)
        self.repairGame.mousePicker.setCollisionMask(SAW_COLLIDE_MASK)
        self.sawButton.activate()

    def exitGame(self):
        RepairMincroGame.exitGame(self)
        self.sawButton.deactivate()
        self.repairGame.mousePicker.clearCollisionMask()
        taskMgr.remove('SawingGame.updateSawTask')
        self.splitBoardSequence.clearToInitial()
        self.dropBoardSequence.clearToInitial()
        localAvatar.guiMgr._showCursor()

    def enterOutro(self):
        RepairMincroGame.enterOutro(self)
        self.repairGame.d_reportMincroGameScore(150)
Пример #4
0
class PartyEditorGridElement(DirectButton):
    __module__ = __name__
    notify = directNotify.newCategory('PartyEditorGridElement')

    def __init__(self, partyEditor, id, isDecoration, checkSoldOutAndPaidStatusAndAffordability, **kw):
        self.partyEditor = partyEditor
        self.id = id
        self.isDecoration = isDecoration
        self.checkSoldOutAndPaidStatusAndAffordability = checkSoldOutAndPaidStatusAndAffordability
        if self.isDecoration:
            self.name = TTLocalizer.PartyDecorationNameDict[self.id]['editor']
            colorList = ((1.0, 1.0, 1.0, 1.0),
             (0.0, 0.0, 1.0, 1.0),
             (0.0, 1.0, 1.0, 1.0),
             (0.5, 0.5, 0.5, 1.0))
            self.geom = self.partyEditor.partyPlanner.gui.find('**/%s' % PartyGlobals.DecorationInformationDict[self.id]['gridAsset'])
        else:
            self.name = TTLocalizer.PartyActivityNameDict[self.id]['editor']
            colorList = ((1.0, 1.0, 1.0, 1.0),
             (0.0, 1.0, 0.0, 1.0),
             (1.0, 1.0, 0.0, 1.0),
             (0.5, 0.5, 0.5, 1.0))
            self.geom = self.partyEditor.partyPlanner.gui.find('**/%s' % PartyGlobals.ActivityInformationDict[self.id]['gridAsset'])
        optiondefs = (('geom', self.geom, None),
         ('geom_scale', 1.0, None),
         ('geom_color', colorList[0], None),
         ('geom1_color', colorList[0], None),
         ('geom2_color', colorList[0], None),
         ('geom3_color', colorList[0], None),
         ('relief', None, None))
        self.defineoptions(kw, optiondefs)
        DirectButton.__init__(self, self.partyEditor.parent)
        self.initialiseoptions(PartyEditorGridElement)
        self.setName('%sGridElement' % self.name)
        self.bind(DirectGuiGlobals.B1PRESS, self.clicked)
        self.bind(DirectGuiGlobals.B1RELEASE, self.released)
        self.bind(DirectGuiGlobals.ENTER, self.mouseEnter)
        self.bind(DirectGuiGlobals.EXIT, self.mouseExit)
        self.uprightNodePath = NodePath('%sUpright' % self.name)
        self.uprightNodePath.reparentTo(self)
        rollOverZOffset = self.getGridSize()[1] / 30.0
        self.rolloverTitle = DirectLabel(relief=None, parent=self.uprightNodePath, pos=Point3(0.0, 0.0, rollOverZOffset), text=self.name, text_fg=(1.0, 1.0, 1.0, 1.0), text_shadow=(0.0, 0.0, 0.0, 1.0), text_scale=0.075)
        self.rolloverTitle.stash()
        self.stash()
        self.overValidSquare = False
        self.lastValidPosition = None
        self.setColorScale(0.9, 0.9, 0.9, 0.7)
        self.setTransparency(True)
        self.mouseOverTrash = False
        self.centerGridSquare = None
        return

    def getCorrectRotation(self):
        r = self.getR()
        if r == 90.0:
            r = 270.0
        elif r == 270.0:
            r = 90.0
        if self.id == PartyGlobals.ActivityIds.PartyCannon:
            return PartyUtils.convertDegreesToPartyGrid(r + 180.0)
        return PartyUtils.convertDegreesToPartyGrid(r)

    def getDecorationTuple(self, x, y):
        return (self.id,
         self.centerGridSquare.x,
         PartyGlobals.PartyEditorGridSize[1] - 1 - self.centerGridSquare.y,
         self.getCorrectRotation())

    def getActivityTuple(self, x, y):
        return (self.id,
         self.centerGridSquare.x,
         PartyGlobals.PartyEditorGridSize[1] - 1 - self.centerGridSquare.y,
         self.getCorrectRotation())

    def attach(self, mouseEvent):
        PartyEditorGridElement.notify.debug('attached grid element %s' % self.name)
        taskMgr.remove('gridElementDragTask%s' % self.name)
        vWidget2render2d = self.getPos(render2d)
        vMouse2render2d = Point3(mouseEvent.getMouse()[0], 0, mouseEvent.getMouse()[1])
        taskMgr.add(self.elementDragTask, 'gridElementDragTask%s' % self.name)
        self.unstash()
        self.rolloverTitle.unstash()
        self.uprightNodePath.reparentTo(self)
        self.setPosHprToDefault()

    def elementDragTask(self, state):
        mwn = base.mouseWatcherNode
        if mwn.hasMouse():
            vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
            newPos = vMouse2render2d
            if newPos[0] > PartyGlobals.PartyEditorGridBounds[0][0] and newPos[0] < PartyGlobals.PartyEditorGridBounds[1][0] and newPos[2] < PartyGlobals.PartyEditorGridBounds[0][1] and newPos[2] > PartyGlobals.PartyEditorGridBounds[1][1]:
                centerGridSquare = self.snapToGrid(newPos)
                if centerGridSquare is not None:
                    self.centerGridSquare = centerGridSquare
                    if not self.overValidSquare:
                        self.setOverValidSquare(True)
                    if self.mouseOverTrash:
                        self.setOverTrash(False)
                    return Task.cont
            if self.id != PartyGlobals.ActivityIds.PartyClock and newPos[0] > PartyGlobals.PartyEditorTrashBounds[0][0] and newPos[0] < PartyGlobals.PartyEditorTrashBounds[1][0] and newPos[2] < PartyGlobals.PartyEditorTrashBounds[0][1] and newPos[2] > PartyGlobals.PartyEditorTrashBounds[1][1]:
                if not self.mouseOverTrash:
                    self.setOverTrash(True)
            elif self.mouseOverTrash:
                self.setOverTrash(False)
            self.setPos(render2d, newPos)
            if self.overValidSquare:
                self.setOverValidSquare(False)
        return Task.cont

    def setOverTrash(self, value):
        self.mouseOverTrash = value
        if value:
            self.partyEditor.trashCanButton['state'] = DirectGuiGlobals.DISABLED
            self.setColorScale(1.0, 0.0, 0.0, 1.0)
        else:
            self.partyEditor.trashCanButton['state'] = DirectGuiGlobals.NORMAL
            self.setColorScale(0.9, 0.9, 0.9, 0.7)

    def setOverValidSquare(self, value):
        self.overValidSquare = value
        if value:
            self.setColorScale(1.0, 1.0, 1.0, 1.0)
        else:
            self.setColorScale(0.9, 0.9, 0.9, 0.7)

    def removeFromGrid(self):
        if self.centerGridSquare is not None:
            self.partyEditor.partyEditorGrid.removeElement(self.centerGridSquare, self.getGridSize())
        self.setOverValidSquare(False)
        self.lastValidPosition = None
        self.stash()
        return

    def snapToGrid(self, newPos):
        gridSquare = self.getGridSquareFromPosition(newPos)
        if gridSquare == None:
            self.setPosHprToDefault()
            self.setPos(render2d, newPos)
            return
        elif not self.partyEditor.partyEditorGrid.checkGridSquareForAvailability(gridSquare, self.getGridSize()):
            self.setPos(render2d, newPos)
            return
        self.setPosHprBasedOnGridSquare(gridSquare)
        return gridSquare

    def getGridSize(self):
        if self.isDecoration:
            return PartyGlobals.DecorationInformationDict[self.id]['gridsize']
        else:
            return PartyGlobals.ActivityInformationDict[self.id]['gridsize']

    def setPosHprToDefault(self):
        self.setR(0.0)
        self.uprightNodePath.setR(0.0)

    def setPosHprBasedOnGridSquare(self, gridSquare):
        gridPos = gridSquare.getPos()
        if self.getGridSize()[0] % 2 == 0:
            gridPos.setX(gridPos[0] + PartyGlobals.PartyEditorGridSquareSize[0] / 2.0)
        if self.getGridSize()[1] % 2 == 0:
            gridPos.setZ(gridPos[2] + PartyGlobals.PartyEditorGridSquareSize[1] / 2.0)
        if self.id != PartyGlobals.ActivityIds.PartyFireworks:
            if gridPos[0] > PartyGlobals.PartyEditorGridCenter[0] + PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(90.0)
                self.uprightNodePath.setR(-90.0)
            elif gridPos[0] < PartyGlobals.PartyEditorGridCenter[0] - PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(270.0)
                self.uprightNodePath.setR(-270.0)
            elif gridPos[2] < PartyGlobals.PartyEditorGridCenter[1] - PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(180.0)
                self.uprightNodePath.setR(-180.0)
            else:
                self.setR(0.0)
                self.uprightNodePath.setR(0.0)
        else:
            self.setR(270.0)
            self.uprightNodePath.setR(-270.0)
        self.setPos(render2d, gridPos)
        self.lastValidPosition = gridPos

    def getGridSquareFromPosition(self, newPos):
        localX = newPos[0] - PartyGlobals.PartyEditorGridBounds[0][0]
        localY = newPos[2] - PartyGlobals.PartyEditorGridBounds[1][1]
        x = int(localX / PartyGlobals.PartyEditorGridSquareSize[0])
        y = int(localY / PartyGlobals.PartyEditorGridSquareSize[1])
        y = PartyGlobals.PartyEditorGridSize[1] - 1 - y
        return self.partyEditor.partyEditorGrid.getGridSquare(x, y)

    def detach(self, mouseEvent):
        taskMgr.remove('gridElementDragTask%s' % self.name)
        self.rolloverTitle.stash()
        if self.overValidSquare:
            self.partyEditor.partyEditorGrid.registerNewElement(self, self.centerGridSquare, self.getGridSize())
            self.partyEditor.updateCostsAndBank()
            self.partyEditor.handleMutuallyExclusiveActivities()
        elif self.lastValidPosition is not None:
            if self.mouseOverTrash:
                self.partyEditor.trashCanButton['state'] = DirectGuiGlobals.NORMAL
                self.lastValidPosition = None
                self.partyEditor.updateCostsAndBank()
                self.stash()
            else:
                self.setPos(render2d, self.lastValidPosition)
                self.setOverValidSquare(True)
                self.partyEditor.partyEditorGrid.registerNewElement(self, self.centerGridSquare, self.getGridSize())
                self.partyEditor.updateCostsAndBank()
                self.partyEditor.handleMutuallyExclusiveActivities()
        else:
            self.stash()
        self.checkSoldOutAndPaidStatusAndAffordability()
        return

    def placeInPartyGrounds(self, desiredXY = None):
        self.centerGridSquare = self.partyEditor.partyEditorGrid.getClearGridSquare(self.getGridSize(), desiredXY)
        if self.centerGridSquare is not None:
            self.setOverValidSquare(True)
            self.unstash()
            self.setPosHprBasedOnGridSquare(self.centerGridSquare)
            self.partyEditor.partyEditorGrid.registerNewElement(self, self.centerGridSquare, self.getGridSize())
            self.partyEditor.updateCostsAndBank()
            self.partyEditor.partyPlanner.instructionLabel['text'] = TTLocalizer.PartyPlannerEditorInstructionsPartyGrounds
            self.checkSoldOutAndPaidStatusAndAffordability()
            return True
        else:
            return False
        return

    def clicked(self, mouseEvent):
        PartyEditorGridElement.notify.debug('clicked grid element %s' % self.name)
        if self.centerGridSquare is not None:
            self.attach(mouseEvent)
            self.partyEditor.partyEditorGrid.removeElement(self.centerGridSquare, self.getGridSize())
        return

    def released(self, mouseEvent):
        PartyEditorGridElement.notify.debug('released grid element %s' % self.name)
        self.detach(mouseEvent)

    def mouseEnter(self, mouseEvent):
        parent = self.getParent()
        self.reparentTo(parent)
        self.rolloverTitle.unstash()

    def mouseExit(self, mouseEvent):
        self.rolloverTitle.stash()

    def destroy(self):
        self.unbind(DirectGuiGlobals.B1PRESS)
        self.unbind(DirectGuiGlobals.B1RELEASE)
        self.unbind(DirectGuiGlobals.ENTER)
        self.unbind(DirectGuiGlobals.EXIT)
        DirectButton.destroy(self)
class RepairMincroGame(DirectFrame, FSM.FSM):
    readySound = None
    goSound = None
    completeSound = None
    
    def __init__(self, repairGame, name, startText):
        DirectFrame.__init__(self, parent = repairGame.gui, relief = None)
        FSM.FSM.__init__(self, '%sFSM' % name)
        self.defaultTransitions = {
            'Idle': [
                'Intro',
                'Final'],
            'Intro': [
                'Game',
                'Idle',
                'Final'],
            'Game': [
                'Outro',
                'Idle',
                'Final'],
            'Outro': [
                'Idle',
                'Final'],
            'Final': [] }
        self.name = name
        self.repairGame = repairGame
        self.startText = startText
        self._initVars()
        self._initAudio()
        self._initVisuals()
        self._initIntervals()
        self.request('Idle')

    
    def _initVars(self):
        self.complete = False
        self.difficulty = 0

    
    def _initAudio(self):
        if not self.readySound:
            RepairMincroGame.readySound = loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_GENERAL_READY)
            RepairMincroGame.goSound = loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_GENERAL_GO)
            RepairMincroGame.completeSound = loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_GENERAL_GAMECOMPLETE)
        

    
    def _initVisuals(self):
        self.countDownLabel = DirectLabel(text = self.startText, text_fg = (1.0, 1.0, 1.0, 1.0), text_shadow = (0.0, 0.0, 0.0, 1.0), text_font = PiratesGlobals.getPirateFont(), scale = (0.16, 0.16, 0.16), pos = (0.0, 0.0, 0.14999999999999999), parent = self, relief = None, textMayChange = 1)
        self.countDownLabel.setBin('fixed', 37)
        self.winLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Win, text_fg = (1.0, 1.0, 1.0, 1.0), text_font = PiratesGlobals.getPirateFont(), text_shadow = (0.0, 0.0, 0.0, 1.0), scale = (0.16, 0.16, 0.16), pos = RepairGlobals.Common.youWinPos[self.name], relief = None, parent = self)
        self.winLabel.setBin('fixed', 37)
        self.winLabel.stash()
        self.scoreLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Win, text_fg = (1.0, 1.0, 1.0, 1.0), text_font = PiratesGlobals.getPirateFont(), text_shadow = (0.0, 0.0, 0.0, 1.0), scale = (0.10000000000000001, 0.10000000000000001, 0.10000000000000001), pos = RepairGlobals.Common.scorePos[self.name], relief = None, parent = self)
        self.scoreLabel.setBin('fixed', 37)
        self.scoreLabel.stash()
        self.postWinLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Pick_New_Game, text_fg = (1.0, 1.0, 1.0, 1.0), text_font = PiratesGlobals.getPirateFont(), text_shadow = (0.0, 0.0, 0.0, 1.0), scale = (0.14000000000000001, 0.14000000000000001, 0.14000000000000001), pos = RepairGlobals.Common.youWinPos[self.name], relief = None, textMayChange = 1, parent = self.repairGame.gui)
        self.postWinLabel.setBin('fixed', 37)
        self.postWinLabel.stash()

    
    def _initIntervals(self):
        normalPos = Vec3(0.0, 0.0, 0.14999999999999999)
        belowScreenPos = Vec3(normalPos.getX(), normalPos.getY(), normalPos.getZ() - 0.25)
        aboveScreenPos = Vec3(normalPos.getX(), normalPos.getY(), normalPos.getZ() + 0.25)
        self.introSequence = Sequence(Func(self.setCountDown, PLocalizer.Minigame_Repair_Countdown_Ready), Parallel(LerpFunc(self.countDownLabel.setPos, fromData = belowScreenPos, toData = normalPos, duration = 0.25), LerpFunc(self.countDownLabel.setAlphaScale, fromData = 0.0, toData = 1.0, duration = 0.25)), Func(self.readySound.play), Wait(0.5), Parallel(LerpFunc(self.countDownLabel.setPos, fromData = normalPos, toData = aboveScreenPos, duration = 0.25), LerpFunc(self.countDownLabel.setAlphaScale, fromData = 1.0, toData = 0.0, duration = 0.25)), Func(self.setCountDown, self.startText), Parallel(LerpFunc(self.countDownLabel.setPos, fromData = belowScreenPos, toData = normalPos, duration = 0.25), LerpFunc(self.countDownLabel.setAlphaScale, fromData = 0.0, toData = 1.0, duration = 0.25)), Func(self.goSound.play), Wait(0.5), Parallel(LerpFunc(self.countDownLabel.setPos, fromData = normalPos, toData = aboveScreenPos, duration = 0.25), LerpFunc(self.countDownLabel.setAlphaScale, fromData = 1.0, toData = 0.0, duration = 0.25)), Func(self.request, 'Game'), name = 'RepairMincroGame.introSequence')
        normalPos = Vec3(RepairGlobals.Common.youWinPos[self.name])
        normalScorePos = Vec3(RepairGlobals.Common.scorePos[self.name])
        belowScreenPos = Vec3(normalPos.getX(), normalPos.getY(), normalPos.getZ() - 0.25)
        aboveScreenPos = Vec3(normalPos.getX(), normalPos.getY(), normalPos.getZ() + 0.25)
        belowScreenScorePos = Vec3(normalScorePos.getX(), normalScorePos.getY(), normalScorePos.getZ() - 0.25)
        aboveScreenScorePos = Vec3(normalScorePos.getX(), normalScorePos.getY(), normalScorePos.getZ() + 0.25)
        self.outroSequence = Sequence(Func(self.winLabel.setAlphaScale, 0), Func(self.scoreLabel.setAlphaScale, 0), Func(self.postWinLabel.setAlphaScale, 0), Func(self.winLabel.setPos, belowScreenPos), Func(self.scoreLabel.setPos, belowScreenScorePos), Func(self.postWinLabel.setPos, belowScreenPos), Func(self.setScoreLabelText), Func(self.winLabel.unstash), Func(self.scoreLabel.unstash), Func(self.postWinLabel.unstash), Parallel(LerpFunc(self.scoreLabel.setPos, fromData = belowScreenScorePos, toData = normalScorePos, duration = 0.25), LerpFunc(self.scoreLabel.setAlphaScale, fromData = 0.0, toData = 1.0, duration = 0.25), LerpFunc(self.winLabel.setPos, fromData = belowScreenPos, toData = normalPos, duration = 0.25), LerpFunc(self.winLabel.setAlphaScale, fromData = 0.0, toData = 1.0, duration = 0.25)), Func(self.completeSound.play), Wait(1.0), Parallel(LerpFunc(self.winLabel.setPos, fromData = normalPos, toData = aboveScreenPos, duration = 0.25), LerpFunc(self.winLabel.setAlphaScale, fromData = 1.0, toData = 0.0, duration = 0.25), LerpFunc(self.scoreLabel.setPos, fromData = normalScorePos, toData = aboveScreenScorePos, duration = 0.25), LerpFunc(self.scoreLabel.setAlphaScale, fromData = 1.0, toData = 0.0, duration = 0.25)), Func(self.winLabel.stash), Func(self.scoreLabel.stash), Func(self.stashPostWinLabelIfCycleComplete), Wait(0.25), Parallel(LerpFunc(self.postWinLabel.setPos, fromData = belowScreenPos, toData = normalPos, duration = 0.25), LerpFunc(self.postWinLabel.setAlphaScale, fromData = 0.0, toData = 1.0, duration = 0.25)), name = 'outroSequence')
        self.cleanupSequence = Sequence(Parallel(LerpFunc(self.postWinLabel.setPos, fromData = normalPos, toData = aboveScreenPos, duration = 0.5), LerpFunc(self.postWinLabel.setAlphaScale, fromData = 1.0, toData = 0.0, duration = 0.5), LerpFunc(self.scoreLabel.setAlphaScale, fromData = 1.0, toData = 0.0, duration = 0.5)), Func(self.scoreLabel.stash), Func(self.postWinLabel.stash), name = 'cleanupSequence')

    
    def updatePostWinLabel(self):
        if self.repairGame.isThereAnOpenGame():
            self.postWinLabel['text'] = PLocalizer.Minigame_Repair_Pick_New_Game
        else:
            self.postWinLabel['text'] = PLocalizer.Minigame_Repair_Waiting_For_Players
        self.postWinLabel.setText()

    
    def setScoreLabelText(self):
        labelSet = False
        for i in [
            0,
            1,
            2]:
            if not labelSet:
                percent = self.difficulty / self.repairGame.difficultyMax
                dif = RepairGlobals.Common.speedThresholds[self.name][i][1] - RepairGlobals.Common.speedThresholds[self.name][i][0]
                goalTime = RepairGlobals.Common.speedThresholds[self.name][i][0] + dif * percent
                if self.repairGame.repairClock.gameTime < goalTime:
                    labelSet = True
                    self.scoreLabel['text'] = PLocalizer.Minigame_Repair_Speed_Thresholds[i]
                
            self.repairGame.repairClock.gameTime < goalTime
        
        if not labelSet:
            self.scoreLabel['text'] = PLocalizer.Minigame_Repair_Speed_Thresholds[3]
        

    
    def stashPostWinLabelIfCycleComplete(self):
        if self.repairGame.isCycleComplete():
            self.postWinLabel.stash()
        

    
    def setDifficulty(self, difficulty):
        self.difficulty = difficulty

    
    def setCountDown(self, text):
        self.countDownLabel['text'] = text
        self.countDownLabel.setText()
        self.countDownLabel.setAlphaScale(0.0)

    
    def destroy(self):
        DirectFrame.destroy(self)
        self.countDownLabel.destroy()
        del self.countDownLabel
        self.winLabel.destroy()
        del self.winLabel
        self.introSequence.clearToInitial()
        del self.introSequence
        self.outroSequence.clearToInitial()
        del self.outroSequence
        self.cleanupSequence.clearToInitial()
        del self.cleanupSequence
        del self.repairGame
        self.cleanup()

    
    def reset(self):
        self.complete = False
        self.repairGame.repairClock.stop()

    
    def enterIdle(self):
        self.stash()

    
    def exitIdle(self):
        pass

    
    def enterIntro(self):
        self.unstash()
        self.countDownLabel.unstash()
        self.introSequence.start()
        self.reset()
        self.countDownLabel.reparentTo(self)

    
    def exitIntro(self):
        self.countDownLabel.stash()
        self.introSequence.clearToInitial()

    
    def enterGame(self):
        self.repairGame.repairClock.restart()

    
    def exitGame(self):
        self.repairGame.repairClock.pause()

    
    def enterOutro(self):
        self.outroSequence.start()
        self.complete = True

    
    def exitOutro(self):
        self.outroSequence.finish()
        self.cleanupSequence.start()
        self.reset()
        self.repairGame.gui.clearTutorial()
        self.repairGame.gui.clearTitle()

    
    def enterFinal(self):
        pass
Пример #6
0
class Avatar(ToonTalker.ToonTalker, Actor, AvatarShared):
    """
    Client side implementation of the base Avatar.

    An Avatar is an animatable character, playable or non-playable, that can be involved
    in combat. Also has the ability to chat, have a nametag, hitbox, and more.
    """

    RealShadows = ConfigVariableBool('want-real-shadows', False)
    AvatarMovedEpsilon = 0.05

    def __init__(self, mat=0):
        try:
            self.Avatar_initialized
            return
        except:
            self.Avatar_initialized = 1

        ToonTalker.ToonTalker.__init__(self)
        #BasePhysicsObject.__init__(self)
        AvatarShared.__init__(self)
        Actor.__init__(self, None, None, None, flattenable=0, setFinal=1)

        # All avatars should be ambient boosted to help them stand out more in BSP levels.
        self.setAmbientBoost()

        self.shapeGroup = CIGlobals.WallGroup | CIGlobals.CharacterGroup

        #self.getGeomNode().showThrough(CIGlobals.ShadowCameraBitmask)

        self.usedAnims = []

        self.moveAnimProperties = {}

        self.mat = mat
        self.chat = ''

        self.nametagNodePath = None
        self.__nameVisible = 1
        self.nametag = NametagGroup()
        self.nametag.setAvatar(self)
        font = CIGlobals.getToonFont()
        self.nametag.setFont(font)
        self.nametag.setChatFont(font)
        self.nametag3d = self.attachNewNode('nametag3d')
        self.nametag3d.setTag('cam', 'nametag')

        self.setTwoSided(False)

        self.forwardSpeed = 0.0
        self.rotateSpeed = 0.0
        self.strafeSpeed = 0.0
        self.currentSpeed = 0.0
        self.standWalkRunReverse = None
        self.currentMoveAction = None

        self.enableBlend()

        self.showNametagInMargins = True
        self.avatarType = None
        self.charName = None
        self.tag = None

        self.splashEffect = None
        self.splashSound = None

        self.shadow = None

        self.prevPos = Point3(0)
        self.wake = None
        self.lastWakeTime = 0.0

        self.thoughtInProg = False

        self.shadowFloorToggle = False
        self.avatarFloorToggle = False
        self.floorTask = taskMgr.add(self.__keepOnFloorTask,
                                     "Avatar.keepOnFloor",
                                     sort=30)

        self.ragdoll = None
        self.ragdollMode = False

        self.healthLabel = None
        self.healthLabelTrack = None
        self.dmgFadeIval = None

        self.forcedTorsoAnim = None
        self.lastForcedTorsoAnim = None

        self.activityTrack = None
        self.wasDoingActivity = False

        self.fadeTrack = None

        self.playedAnims = None

        self.chatSoundTable = {}

        return

    def doScaleUp(self):
        self.scaleInterval(2.0, (1, 1, 1), (0.01, 0.01, 0.01)).start()

    def recordUsedAnim(self, animName):
        if not animName in self.usedAnims:
            self.usedAnims.append(animName)

    def clearFadeTrack(self):
        if self.fadeTrack:
            self.fadeTrack.finish()
        self.fadeTrack = None

    def fadeOut(self, time=1.0):
        self.clearFadeTrack()
        self.fadeTrack = Sequence(
            Func(self.setTransparency, 1),
            LerpColorScaleInterval(self, time, (1, 1, 1, 0), (1, 1, 1, 1)),
            Func(self.hide))
        self.fadeTrack.start()

    def fadeIn(self, time=1.0):
        self.clearFadeTrack()
        self.fadeTrack = Sequence(
            Func(self.setTransparency, 1),
            LerpColorScaleInterval(self, time, (1, 1, 1, 1), (1, 1, 1, 0)),
            Func(self.setTransparency, 0))
        self.fadeTrack.start()

    def getAttackMgr(self):
        return base.cr.attackMgr

    def stopActivity(self):
        if self.activityTrack:
            self.activityTrack.finish()
        self.activityTrack = None
        self.doingActivity = False

    def setActivity(self, activity, timestamp):
        AvatarShared.setActivity(self, activity, timestamp)

        self.stopActivity()

        if activity == -1 or activity not in self.activities:
            return

        self.doingActivity = True
        ts = globalClockDelta.localElapsedTime(timestamp)
        act = self.activities[activity]
        loop = act.shouldLoop()
        self.activityTrack = act.doActivity()
        self.activityTrack.start()
        #if loop:
        #    self.activityTrack.loop()
        #else:
        #    self.activityTrack.start()

    def setForcedTorsoAnim(self, anim):
        self.forcedTorsoAnim = anim

    def hasForcedTorsoAnim(self):
        return self.forcedTorsoAnim is not None

    def getForcedTorsoAnim(self):
        return self.forcedTorsoAnim

    def clearForcedTorsoAnim(self):

        if not self.forcedTorsoAnim is None:
            # Let's switch our current torso and head animation to the
            # animation the legs are running.
            legs = self.getLowerBodySubpart()[0]
            legAnimation = self.getCurrentAnim(partName=legs)
            frame = self.getCurrentFrame(partName=legs, animName=legAnimation)

            def __anim(partName):
                self.stop(partName=partName)
                self.play(animName=legAnimation,
                          partName=partName,
                          fromFrame=frame)

            parts = self.getUpperBodySubpart()
            for part in parts:
                __anim(part)

        self.forcedTorsoAnim = None

    def setPlayRate(self, rate, animName, partName=None):
        if partName or not self.forcedTorsoAnim:
            Actor.setPlayRate(self, rate, animName, partName)
        else:
            parts = self.getLowerBodySubpart() + self.getUpperBodySubpart()
            for part in parts:
                Actor.setPlayRate(self, rate, animName, part)

    def play(self, animName, partName=None, fromFrame=None, toFrame=None):
        self.recordUsedAnim(animName)

        lowerHalfNames = self.getLowerBodySubpart()
        if self.forcedTorsoAnim is None or (not (partName in lowerHalfNames)):
            Actor.play(self,
                       animName,
                       partName=partName,
                       fromFrame=fromFrame,
                       toFrame=toFrame)
        else:
            # The torso and the head must stay in its current animation.
            # Let's only update the pants and the legs animation.
            for part in lowerHalfNames:
                Actor.play(self,
                           animName,
                           partName=part,
                           fromFrame=fromFrame,
                           toFrame=toFrame)

    def loop(self,
             animName,
             restart=1,
             partName=None,
             fromFrame=None,
             toFrame=None):
        self.recordUsedAnim(animName)

        lowerHalfNames = self.getLowerBodySubpart()
        if self.forcedTorsoAnim is None or (not (partName in lowerHalfNames)):
            return Actor.loop(self,
                              animName,
                              restart=restart,
                              partName=partName,
                              fromFrame=fromFrame,
                              toFrame=toFrame)
        else:
            # The torso and the head must stay in its current animation.
            # Let's only update the pants and the legs animation.
            for index, part in enumerate(lowerHalfNames):
                output = Actor.loop(self,
                                    animName,
                                    restart=restart,
                                    partName=part,
                                    fromFrame=fromFrame,
                                    toFrame=toFrame)

    def pingpong(self,
                 animName,
                 restart=1,
                 partName=None,
                 fromFrame=None,
                 toFrame=None):
        self.recordUsedAnim(animName)

        lowerHalfNames = self.getLowerBodySubpart()
        if self.forcedTorsoAnim is None or (not (partName in lowerHalfNames)):
            Actor.pingpong(self,
                           animName,
                           restart=restart,
                           partName=partName,
                           fromFrame=fromFrame,
                           toFrame=toFrame)
        else:
            # The torso and the head must stay in its current animation.
            # Let's only update the pants and the legs animation.
            for part in lowerHalfNames:
                Actor.pingpong(self,
                               animName,
                               restart=restart,
                               partName=part,
                               fromFrame=fromFrame,
                               toFrame=toFrame)

    def getMoveAction(self, forward, rotate, strafe):
        return 0

    def performAnim(self, anim, partName=None, animInfo=None):
        if not animInfo:
            self.loop(anim, partName=partName)
        else:
            extraArgs = animInfo.get('args', {})
            meth = animInfo.get('method', 'loop')
            if meth == 'loop':
                self.loop(anim, **extraArgs)
            elif meth == 'pingpong':
                self.pingpong(anim, **extraArgs)

    def resetMoveAnims(self, anim=None, anim2=None):
        self.resetAnimBlends()
        if self.forcedTorsoAnim is None:
            self.disableBlend()
            self.stop()
            if anim and anim2:
                self.enableBlend()
                self.performAnim(anim, None,
                                 self.moveAnimProperties.get(anim, None))
                self.performAnim(anim2, None,
                                 self.moveAnimProperties.get(anim2, None))
        else:
            parts = self.getLowerBodySubpart()
            for part in parts:
                self.disableBlend(partName=part)
                self.stop(partName=part)
                if anim and anim2:
                    self.enableBlend(partName=part)
                    self.performAnim(anim, part,
                                     self.moveAnimProperties.get(anim, None))
                    self.performAnim(anim2, part,
                                     self.moveAnimProperties.get(anim2, None))

    def resetAnimBlends(self):
        for animName in self.usedAnims:
            self.setControlEffect(animName, 0.0)
        self.usedAnims = []

    def setSpeed(self, forwardSpeed, rotateSpeed, strafeSpeed=0.0):
        if self.ragdollMode:
            return

        self.forwardSpeed = forwardSpeed
        self.rotateSpeed = rotateSpeed
        self.strafeSpeed = strafeSpeed

        action = None
        if self.standWalkRunReverse != None and not self.doingActivity:
            action = self.getMoveAction(forwardSpeed, rotateSpeed, strafeSpeed)
            anim, anim2, minSpeed, maxSpeed, rate, rate2 = self.standWalkRunReverse[
                action]
            if self.currentMoveAction != action or self.lastForcedTorsoAnim != self.forcedTorsoAnim or self.wasDoingActivity:
                self.playingAnim = anim
                self.lastForcedTorsoAnim = self.forcedTorsoAnim
                self.currentMoveAction = action
                self.resetMoveAnims(anim, anim2)

            speed = max(
                Vec3(forwardSpeed, strafeSpeed, rotateSpeed / 15.0).length(),
                minSpeed)
            self.currentSpeed = speed
            ratio = speed / maxSpeed
            ratioClamped = CIGlobals.clamp(ratio, 0, 1)
            self.setControlEffect(anim, 1 - ratioClamped)
            self.setControlEffect(anim2, ratioClamped)

            if ratio > 1:
                self.setPlayRate(rate * ratio, anim)
                self.setPlayRate(rate2 * ratio, anim2)
            else:
                self.setPlayRate(rate, anim)
                self.setPlayRate(rate2, anim2)

            self.wasDoingActivity = False
        elif self.doingActivity and not self.wasDoingActivity:
            self.wasDoingActivity = True
            self.resetMoveAnims()

        return action

    def getRightHandNode(self):
        return NodePath("rightHand")

    def getLeftHandNode(self):
        return NodePath("leftHand")

    def getHeadNode(self):
        return NodePath("head")

    def handleHitByToon(self, player, gagId, distance):
        pass

    def handleHitByEnemy(self, enemy, weaponId, distance):
        pass

    def getLowerBodySubpart(self):
        return ["legs"]

    def getUpperBodySubpart(self):
        return ["torso"]

    def taskName(self, name):
        return name + "-" + str(id(self))

    def uniqueName(self, name):
        return name + "-" + str(id(self))

    def setupHealthLabel(self):
        self.healthLabel = DirectLabel(text="",
                                       text_fg=CIGlobals.PositiveTextColor,
                                       scale=0.9,
                                       relief=None,
                                       text_decal=True,
                                       text_font=CIGlobals.getMickeyFont(),
                                       text_align=TextNode.ACenter)
        self.healthLabel.reparentTo(self)
        self.healthLabel.setBillboardPointEye()
        self.healthLabel.stash()
        self.healthLabel.setLightOff(1)
        self.healthLabel.hide(CIGlobals.ShadowCameraBitmask
                              | CIGlobals.ReflectionCameraBitmask)

    def showAndMoveHealthLabel(self, zoffset=0.5, stashWaitTime=1.0):
        self.unstashHpLabel()
        self.stopMovingHealthLabel()
        x = self.nametag3d.getX()
        y = self.nametag3d.getY()
        z = self.nametag3d.getZ()
        moveTrack = LerpPosInterval(self.healthLabel,
                                    duration=0.5,
                                    pos=(x, y, z + zoffset),
                                    startPos=(x, y, z - 2),
                                    blendType='easeOut')
        self.healthLabelTrack = Sequence(moveTrack, Wait(stashWaitTime),
                                         Func(self.stashHpLabel))
        self.healthLabelTrack.start()

    def stopMovingHealthLabel(self):
        if self.healthLabelTrack != None:
            self.healthLabelTrack.pause()
            self.healthLabelTrack = None

    def stashHpLabel(self):
        self.healthLabel.stash()

    def unstashHpLabel(self):
        self.healthLabel.unstash()

    def doDamageFade(self):
        # Stop the current fade interval if it exists.
        if self.dmgFadeIval:
            self.dmgFadeIval.finish()
            self.dmgFadeIval = None

        geomNode = self.getGeomNode()
        # Do a fade effect when we get hit so we are more aware that we were damaged.
        self.dmgFadeIval = Sequence(
            Func(geomNode.setTransparency, 1),
            LerpColorScaleInterval(geomNode,
                                   0.3, (1, 1, 1, 0.5), (1, 1, 1, 1),
                                   blendType='easeOut'),
            LerpColorScaleInterval(geomNode,
                                   0.3, (1, 1, 1, 1), (1, 1, 1, 0.5),
                                   blendType='easeIn'),
            Func(geomNode.setTransparency, 0))
        self.dmgFadeIval.start()

    def announceHealth(self, level, hp, extraId):
        if not self.healthLabel:
            return

        if hp > 0:
            prefix = "+"
        else:
            prefix = ""

        if extraId != -1:
            prefix = self.EXTRAS[extraId] + "\n" + prefix

        if level == 1:
            self.healthLabel["text_fg"] = CIGlobals.PositiveTextColor
            self.healthLabel['text'] = prefix + str(hp)
        else:
            textFg = CIGlobals.NegativeTextColor
            if level == 2:
                textFg = CIGlobals.OrangeTextColor
            elif level == 3:
                textFg = CIGlobals.YellowTextColor
            self.healthLabel["text_fg"] = textFg
            self.healthLabel['text'] = prefix + str(hp)

        self.showAndMoveHealthLabel(1.0 if extraId != -1 else 0.5)

    def doRagdollMode(self,
                      forceX=0,
                      forceY=0,
                      forceZ=0,
                      forcePosX=0,
                      forcePosY=0,
                      forcePosZ=0):
        if self.ragdollMode:
            return

        forceVec = Vec3(forceX, forceY, forceZ)
        forcePos = Vec3(forcePosX, forcePosY, forcePosZ)

        self.stop()
        self.disableRay()
        self.disableShadowRay()
        if self.shadow:
            self.shadow.hide()
        self.disableBodyCollisions()
        if self.ragdoll:
            self.ragdoll.mode = self.ragdoll.RMRagdoll
            self.ragdoll.setEnabled(True)
            self.ragdoll.attachActor()
            self.ragdoll.applyForce(forceVec, forcePos)

        self.ragdollMode = True

    def getSplash(self):
        if not self.splashEffect:
            self.splashEffect = Splash(render)
            self.splashSound = base.loadSfxOnNode(
                "phase_5.5/audio/sfx/AV_jump_in_water.ogg", self.splashEffect)

        return self.splashEffect

    def getWake(self):
        if not self.wake:
            self.wake = Wake(render, self)

        return self.wake

    def splash(self, x, y, z):
        spl = self.getSplash()
        spl.setPos(x, y, z)
        spl.setScale(2)
        spl.play()

        self.splashSound.play()

    def isLocalAvatar(self):
        if not hasattr(base, 'localAvatar'):
            return False
        return self == base.localAvatar

    def initializeRay(self, *args, **kwargs):
        pass

    def enableShadowRay(self):
        self.shadowFloorToggle = True

    def disableShadowRay(self):
        self.shadowFloorToggle = False

    def enableRay(self):
        self.avatarFloorToggle = True

    def disableRay(self):
        self.avatarFloorToggle = False

    def updateFloorHeight(self, z):
        if self.avatarFloorToggle:
            self.setZ(render, z)
        if self.shadowFloorToggle and self.shadow:
            self.shadow.setZ(render, z)

    def __keepOnFloorTask(self, task):
        # First, check if we are above a ground.
        # If so, go onto that.

        if self.isEmpty():
            return task.done

        # Create water ripples
        time = globalClock.getFrameTime()
        delta = time - self.lastWakeTime
        dt = globalClock.getDt()
        pos = self.getPos(render)
        posDelta = (pos - self.prevPos).lengthSquared()
        moveMag = posDelta / dt
        if moveMag > 5.0 and delta > 0.1:
            if hasattr(base, 'waterReflectionMgr'):
                result = base.waterReflectionMgr.isTouchingAnyWater(
                    pos, pos + (0, 0, self.getHeight()))
                if result[0]:
                    self.getWake().createRipple(result[1],
                                                rate=1,
                                                startFrame=4)
                    self.lastWakeTime = time
        self.prevPos = pos

        # Position is updated from server, don't need to move avatar on client
        return task.cont

        if (not self.avatarFloorToggle and not self.shadowFloorToggle
                or moveMag < self.AvatarMovedEpsilon):

            # Avoid unnecessary ray casting.
            return task.cont

        pFrom = self.getPos(render) + (0, 0, 0.1)

        z = None

        pTo = pFrom - (0, 0, 2000)
        aboveResult = base.physicsWorld.rayTestAll(
            pFrom, pTo, CIGlobals.WallGroup | CIGlobals.FloorGroup
            | CIGlobals.StreetVisGroup)
        aboveGround = False
        if aboveResult.hasHits():
            sortedHits = sorted(aboveResult.getHits(),
                                key=lambda hit:
                                (pFrom - hit.getHitPos()).lengthSquared())
            for i in range(len(sortedHits)):
                hit = sortedHits[i]
                node = hit.getNode()
                np = NodePath(node)
                if self.isAncestorOf(np):
                    continue
                z = hit.getHitPos().getZ()
                break

        if z is not None:
            self.updateFloorHeight(z)
            return task.cont

        # We're not above a ground, check above?
        pTo = pFrom + (0, 0, 2000)
        belowResult = base.physicsWorld.rayTestAll(
            pFrom, pTo, CIGlobals.WallGroup | CIGlobals.FloorGroup
            | CIGlobals.StreetVisGroup)
        if belowResult.hasHits():
            sortedHits = sorted(belowResult.getHits(),
                                key=lambda hit:
                                (pFrom - hit.getHitPos()).lengthSquared())
            for i in range(len(sortedHits)):
                hit = sortedHits[i]
                node = hit.getNode()
                np = NodePath(node)
                if self.isAncestorOf(np):
                    continue
                z = hit.getHitPos().getZ()
                break

        if z is not None:
            self.updateFloorHeight(z)

        return task.cont

    def setupPhysics(self, radius=None, height=None):
        if not radius:
            radius = self.getWidth()
        if not height:
            height = self.getHeight()
        self.notify.debug("setupPhysics(r{0}, h{1}) hitboxData: {2}".format(
            radius, height, self.hitboxData))

        # When the height is passed into BulletCapsuleShape, it's
        # talking about the height only of the cylinder part.
        # But what we want is the height of the entire capsule.
        #height -= (radius * 2)
        # The middle of the capsule is the origin by default. Push the capsule shape up
        # so the very bottom of the capsule is the origin.
        zOfs = (height / 2.0) + radius

        capsule = BulletCapsuleShape(radius, height)
        bodyNode = BulletRigidBodyNode('avatarBodyNode')
        bodyNode.addShape(capsule, TransformState.makePos(Point3(0, 0, zOfs)))
        bodyNode.setKinematic(True)
        bodyNode.setPythonTag("avatar", self)

        BasePhysicsObject.setupPhysics(self, bodyNode, True)

        self.stopWaterCheck()

    def isDistributed(self):
        return hasattr(self, 'doId')

    def chatStompComplete(self, text):
        chatType = CHAT_LONG

        if "ooo" in text.lower():
            chatType = CHAT_HOWL
        elif "!" in text.lower():
            chatType = CHAT_EXCLAIM
        elif "?" in text.lower():
            chatType = CHAT_QUESTION
        elif len(text) <= 9:
            chatType = CHAT_SHORT
        elif 10 <= len(text) <= 19:
            chatType = CHAT_MEDIUM
        elif len(text) >= 20:
            chatType = CHAT_LONG

        snd = self.chatSoundTable.get(chatType, None)
        if isinstance(snd, list):
            snd = random.choice(snd)

        if snd:
            self.playSound(snd)

    def deleteNameTag(self):
        self.deleteNametag3d()

    def disableBodyCollisions(self):
        self.cleanupPhysics()

    def loadAvatar(self):
        self.setupHealthLabel()
        self.setBlend(
            frameBlend=base.config.GetBool("interpolate-frames", False))
        base.avatars.append(self)

    def disable(self):
        try:
            self.Avatar_disabled
        except:
            self.Avatar_disabled = 1
            if self.ragdoll:
                self.ragdoll.cleanup()
            self.ragdoll = None
            self.clearFadeTrack()
            if self in base.avatars:
                base.avatars.remove(self)
            if self.dmgFadeIval:
                self.dmgFadeIval.finish()
            self.stopActivity()
            self.dmgFadeIval = None
            self.stopMovingHealthLabel()
            if self.healthLabel:
                self.healthLabel.removeNode()
            self.healthLabel = None
            self.healthLabelTrack = None
            if self.floorTask:
                self.floorTask.remove()
            self.floorTask = None
            self.disableRay()
            self.deleteNametag3d()
            self.nametag.destroy()
            del self.nametag
            self.nametag3d.removeNode()
            self.nametag3d = None
            self.deleteShadow()
            self.removeLoopTask()
            self.mat = None
            self.tag = None
            self.chat = None
            self.avatarType = None
            self.charName = None
            self.nameTag = None
            self.cleanupPhysics()
            self.moveAnimProperties = None
            self.chatSoundTable = None

            self.lastWakeTime = None
            self.prevPos = None

            if self.wake:
                self.wake.destroy()
                self.wake = None

            if self.splashEffect:
                self.splashEffect.destroy()
                self.splashEffect = None

            self.splashSound = None

            self.avatarFloorToggle = None
            self.shadowFloorToggle = None

            Actor.cleanup(self)

    def delete(self):
        try:
            self.Avatar_deleted
        except:
            self.Avatar_deleted = 1
            self.removeLoopTask()
            AvatarShared.delete(self)
            Actor.delete(self)

    def getNameTag(self):
        return self.nametag3d

    def getNametagJoints(self):
        return []

    def setChat(self, chatString=None):
        print("setChat on", self.__class__.__name__)
        self.nametag.setChatType(NametagGlobals.CHAT)
        shouldClear = self.autoClearChat
        if self.isThought(chatString):
            self.thoughtInProg = True
            chatString = self.removeThoughtPrefix(chatString)
            self.nametag.setChatBalloonType(NametagGlobals.THOUGHT_BALLOON)
            shouldClear = False
        else:
            self.thoughtInProg = False
            self.nametag.setChatBalloonType(NametagGlobals.CHAT_BALLOON)
        self.nametag.setChatText(chatString, timeout=shouldClear)

    def setName(self, nameString=None, charName=None, createNow=0):
        if not nameString:
            return
        AvatarShared.setName(self, nameString)
        if charName:
            self.charName = charName
        if createNow:
            self.setupNameTag()

    def getName(self):
        return AvatarShared.getName(self)

    def setupNameTag(self, tempName=None):
        if not self._name and not tempName:
            return
        if tempName:
            name = tempName
        else:
            name = self._name
        offset = 0.0
        z = 0.0
        if self.avatarType:
            if self.avatarType in [CIGlobals.Suit]:
                offset = 1.0
                z = self.getHeight()
            elif self.avatarType == CIGlobals.Toon:
                offset = 0.5

        self.deleteNametag3d()
        self.initializeNametag3d()

        if self.avatarType == CIGlobals.Toon:
            self.nametag3d.setZ(self.getHeight() + offset)
        elif self.avatarType == CIGlobals.Suit or self.avatarType == CIGlobals.CChar:
            self.nametag3d.setZ(z + offset)

        if self.avatarType == CIGlobals.Suit:
            self.nametag.setFont(CIGlobals.getSuitFont())
            self.nametag.setChatFont(CIGlobals.getSuitFont())
            self.nametag.setNametagColor(
                NametagGlobals.NametagColors[NametagGlobals.CCSuit])
            self.nametag.setActive(0)
        else:
            self.nametag.setFont(CIGlobals.getToonFont())
            self.nametag.setChatFont(CIGlobals.getToonFont())
            self.nametag.setNametagColor(
                NametagGlobals.NametagColors[NametagGlobals.CCOtherPlayer])
        self.nametag.setText(name)
        if self.showNametagInMargins:
            self.nametag.manage(base.marginManager)
        self.nametag.updateAll()

    def setAvatarScale(self, scale):
        self.getGeomNode().setScale(scale)

    def getShadow(self):
        if hasattr(self, 'shadow'):
            if self.shadow:
                return self.shadow
        return None

    def initShadow(self):
        if metadata.USE_REAL_SHADOWS:
            self.shadow = None
        else:
            self.shadow = loader.loadModel(
                "phase_3/models/props/drop_shadow.bam")
            self.shadow.setScale(CIGlobals.ShadowScales[self.avatarType])
            self.shadow.flattenMedium()
            self.shadow.setBillboardAxis(4)
            self.shadow.setColor(0, 0, 0, 0.5, 1)
            if self.avatarType == CIGlobals.Toon:
                self.shadow.reparentTo(
                    self.getPart('legs').find('**/joint_shadow'))
            elif self.avatarType == CIGlobals.Suit:
                self.shadow.reparentTo(self)
            else:
                self.shadow.reparentTo(self)

        self.enableShadowRay()

    def deleteShadow(self):
        if hasattr(self, 'shadow'):
            if self.shadow:
                self.shadow.removeNode()
                self.shadow = None

    def loopFromFrameToZero(self,
                            animName,
                            restart=1,
                            partName=None,
                            fromFrame=None):
        # Loop an animation from a frame, restarting at 0.
        # This is only used in Make A Toon, but could be used in other things,
        # that are not distributed.
        dur = self.getDuration(animName, fromFrame=fromFrame)
        self.play(animName, partName=partName, fromFrame=fromFrame)
        taskName = self.uniqueName('loopTask')
        taskMgr.doMethodLater(dur,
                              self.loopTask,
                              taskName,
                              extraArgs=[animName, restart, partName],
                              appendTask=True)

    def removeLoopTask(self):
        taskMgr.remove(self.uniqueName('loopTask'))

    def removePart(self, partName, lodName="lodRoot"):
        self.removeLoopTask()
        part = Actor.getPart(self, partName, lodName)

        if part:
            Actor.removePart(self, partName, lodName=lodName)

    def loopTask(self, animName, restart, partName, task):
        self.loop(animName, restart, partName)
        return task.done

    def getGhost(self):
        return 0

    def hideName(self):
        nametag3d = self.nametag.getNametag3d()
        nametag3d.hideNametag()
        nametag3d.showChat()
        nametag3d.showThought()
        nametag3d.update()

    def showName(self):
        if self.__nameVisible and not self.getGhost():
            nametag3d = self.nametag.getNametag3d()
            nametag3d.showNametag()
            nametag3d.showChat()
            nametag3d.showThought()
            nametag3d.update()

    def hideNametag2d(self):
        nametag2d = self.nametag.getNametag2d()
        nametag2d.hideNametag()
        nametag2d.hideChat()
        nametag2d.update()

    def showNametag2d(self):
        nametag2d = self.nametag.getNametag2d()
        if not self.getGhost():
            nametag2d.showNametag()
            nametag2d.showChat()
        else:
            nametag2d.hideNametag()
            nametag2d.hideChat()
        nametag2d.update()

    def hideNametag3d(self):
        nametag3d = self.nametag.getNametag3d()
        nametag3d.hideNametag()
        nametag3d.hideChat()
        nametag3d.hideThought()
        nametag3d.update()

    def showNametag3d(self):
        nametag3d = self.nametag.getNametag3d()
        if self.__nameVisible and (not self.getGhost()):
            nametag3d.showNametag()
            nametag3d.showChat()
            nametag3d.showThought()
        else:
            nametag3d.hideNametag()
            nametag3d.hideChat()
            nametag3d.hideThought()
        nametag3d.update()

    def setPickable(self, flag):
        self.nametag.setActive(flag)

    def clickedNametag(self):
        if self.nametag.getActive():
            messenger.send('clickedNametag', [self])

    def initializeNametag3d(self):
        self.deleteNametag3d()
        nametagNode = self.nametag.getNametag3d()
        self.nametagNodePath = self.nametag3d.attachNewNode(nametagNode)

        for cJoint in self.getNametagJoints():
            cJoint.clearNetTransforms()
            cJoint.addNetTransform(nametagNode)

    def deleteNametag3d(self):
        if self.nametagNodePath:
            self.nametagNodePath.removeNode()
            self.nametagNodePath = None

    def getNameVisible(self):
        return self.__nameVisible

    def setNameVisible(self, visible):
        self.__nameVisible = visible
        if visible:
            self.showName()
        else:
            self.hideName()
Пример #7
0
class PartyEditorGridElement(DirectButton):
    notify = directNotify.newCategory('PartyEditorGridElement')

    def __init__(self, partyEditor, id, isDecoration, checkSoldOutAndPaidStatusAndAffordability, **kw):
        self.partyEditor = partyEditor
        self.id = id
        self.isDecoration = isDecoration
        self.checkSoldOutAndPaidStatusAndAffordability = checkSoldOutAndPaidStatusAndAffordability
        if self.isDecoration:
            self.name = TTLocalizer.PartyDecorationNameDict[self.id]['editor']
            colorList = ((1.0, 1.0, 1.0, 1.0),
             (0.0, 0.0, 1.0, 1.0),
             (0.0, 1.0, 1.0, 1.0),
             (0.5, 0.5, 0.5, 1.0))
            self.geom = self.partyEditor.partyPlanner.gui.find('**/%s' % PartyGlobals.DecorationInformationDict[self.id]['gridAsset'])
        else:
            self.name = TTLocalizer.PartyActivityNameDict[self.id]['editor']
            colorList = ((1.0, 1.0, 1.0, 1.0),
             (0.0, 1.0, 0.0, 1.0),
             (1.0, 1.0, 0.0, 1.0),
             (0.5, 0.5, 0.5, 1.0))
            self.geom = self.partyEditor.partyPlanner.gui.find('**/%s' % PartyGlobals.ActivityInformationDict[self.id]['gridAsset'])
        optiondefs = (('geom', self.geom, None),
         ('geom_scale', 1.0, None),
         ('geom_color', colorList[0], None),
         ('geom1_color', colorList[0], None),
         ('geom2_color', colorList[0], None),
         ('geom3_color', colorList[0], None),
         ('relief', None, None))
        self.defineoptions(kw, optiondefs)
        DirectButton.__init__(self, self.partyEditor._parent)
        self.initialiseoptions(PartyEditorGridElement)
        self.setName('%sGridElement' % self.name)
        self.bind(DirectGuiGlobals.B1PRESS, self.clicked)
        self.bind(DirectGuiGlobals.B1RELEASE, self.released)
        self.bind(DirectGuiGlobals.ENTER, self.mouseEnter)
        self.bind(DirectGuiGlobals.EXIT, self.mouseExit)
        self.uprightNodePath = NodePath('%sUpright' % self.name)
        self.uprightNodePath.reparentTo(self)
        rollOverZOffset = self.getGridSize()[1] / 30.0
        self.rolloverTitle = DirectLabel(relief=None, parent=self.uprightNodePath, pos=Point3(0.0, 0.0, rollOverZOffset), text=self.name, text_fg=(1.0, 1.0, 1.0, 1.0), text_shadow=(0.0, 0.0, 0.0, 1.0), text_scale=0.075)
        self.rolloverTitle.stash()
        self.stash()
        self.overValidSquare = False
        self.lastValidPosition = None
        self.setColorScale(0.9, 0.9, 0.9, 0.7)
        self.setTransparency(True)
        self.mouseOverTrash = False
        self.centerGridSquare = None
        return

    def getCorrectRotation(self):
        r = self.getR()
        if r == 90.0:
            r = 270.0
        elif r == 270.0:
            r = 90.0
        if self.id == PartyGlobals.ActivityIds.PartyCannon:
            return PartyUtils.convertDegreesToPartyGrid(r + 180.0)
        return PartyUtils.convertDegreesToPartyGrid(r)

    def getDecorationTuple(self, x, y):
        return (self.id,
         self.centerGridSquare.x,
         PartyGlobals.PartyEditorGridSize[1] - 1 - self.centerGridSquare.y,
         self.getCorrectRotation())

    def getActivityTuple(self, x, y):
        return (self.id,
         self.centerGridSquare.x,
         PartyGlobals.PartyEditorGridSize[1] - 1 - self.centerGridSquare.y,
         self.getCorrectRotation())

    def attach(self, mouseEvent):
        PartyEditorGridElement.notify.debug('attached grid element %s' % self.name)
        taskMgr.remove('gridElementDragTask%s' % self.name)
        vWidget2render2d = self.getPos(render2d)
        vMouse2render2d = Point3(mouseEvent.getMouse()[0], 0, mouseEvent.getMouse()[1])
        taskMgr.add(self.elementDragTask, 'gridElementDragTask%s' % self.name)
        self.unstash()
        self.rolloverTitle.unstash()
        self.uprightNodePath.reparentTo(self)
        self.setPosHprToDefault()

    def elementDragTask(self, state):
        mwn = base.mouseWatcherNode
        if mwn.hasMouse():
            vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
            newPos = vMouse2render2d
            if newPos[0] > PartyGlobals.PartyEditorGridBounds[0][0] and newPos[0] < PartyGlobals.PartyEditorGridBounds[1][0] and newPos[2] < PartyGlobals.PartyEditorGridBounds[0][1] and newPos[2] > PartyGlobals.PartyEditorGridBounds[1][1]:
                centerGridSquare = self.snapToGrid(newPos)
                if centerGridSquare is not None:
                    self.centerGridSquare = centerGridSquare
                    if not self.overValidSquare:
                        self.setOverValidSquare(True)
                    if self.mouseOverTrash:
                        self.setOverTrash(False)
                    return Task.cont
            if self.id != PartyGlobals.ActivityIds.PartyClock and newPos[0] > PartyGlobals.PartyEditorTrashBounds[0][0] and newPos[0] < PartyGlobals.PartyEditorTrashBounds[1][0] and newPos[2] < PartyGlobals.PartyEditorTrashBounds[0][1] and newPos[2] > PartyGlobals.PartyEditorTrashBounds[1][1]:
                if not self.mouseOverTrash:
                    self.setOverTrash(True)
            elif self.mouseOverTrash:
                self.setOverTrash(False)
            self.setPos(render2d, newPos)
            if self.overValidSquare:
                self.setOverValidSquare(False)
        return Task.cont

    def setOverTrash(self, value):
        self.mouseOverTrash = value
        if value:
            self.partyEditor.trashCanButton['state'] = DirectGuiGlobals.DISABLED
            self.setColorScale(1.0, 0.0, 0.0, 1.0)
        else:
            self.partyEditor.trashCanButton['state'] = DirectGuiGlobals.NORMAL
            self.setColorScale(0.9, 0.9, 0.9, 0.7)

    def setOverValidSquare(self, value):
        self.overValidSquare = value
        if value:
            self.setColorScale(1.0, 1.0, 1.0, 1.0)
        else:
            self.setColorScale(0.9, 0.9, 0.9, 0.7)

    def removeFromGrid(self):
        if self.centerGridSquare is not None:
            self.partyEditor.partyEditorGrid.removeElement(self.centerGridSquare, self.getGridSize())
        self.setOverValidSquare(False)
        self.lastValidPosition = None
        self.stash()
        return

    def snapToGrid(self, newPos):
        gridSquare = self.getGridSquareFromPosition(newPos)
        if gridSquare == None:
            self.setPosHprToDefault()
            self.setPos(render2d, newPos)
            return
        elif not self.partyEditor.partyEditorGrid.checkGridSquareForAvailability(gridSquare, self.getGridSize()):
            self.setPos(render2d, newPos)
            return
        self.setPosHprBasedOnGridSquare(gridSquare)
        return gridSquare

    def getGridSize(self):
        if self.isDecoration:
            return PartyGlobals.DecorationInformationDict[self.id]['gridsize']
        else:
            return PartyGlobals.ActivityInformationDict[self.id]['gridsize']

    def setPosHprToDefault(self):
        self.setR(0.0)
        self.uprightNodePath.setR(0.0)

    def setPosHprBasedOnGridSquare(self, gridSquare):
        gridPos = gridSquare.getPos()
        if self.getGridSize()[0] % 2 == 0:
            gridPos.setX(gridPos[0] + PartyGlobals.PartyEditorGridSquareSize[0] / 2.0)
        if self.getGridSize()[1] % 2 == 0:
            gridPos.setZ(gridPos[2] + PartyGlobals.PartyEditorGridSquareSize[1] / 2.0)
        if self.id != PartyGlobals.ActivityIds.PartyFireworks:
            if gridPos[0] > PartyGlobals.PartyEditorGridCenter[0] + PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(90.0)
                self.uprightNodePath.setR(-90.0)
            elif gridPos[0] < PartyGlobals.PartyEditorGridCenter[0] - PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(270.0)
                self.uprightNodePath.setR(-270.0)
            elif gridPos[2] < PartyGlobals.PartyEditorGridCenter[1] - PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(180.0)
                self.uprightNodePath.setR(-180.0)
            else:
                self.setR(0.0)
                self.uprightNodePath.setR(0.0)
        else:
            self.setR(270.0)
            self.uprightNodePath.setR(-270.0)
        self.setPos(render2d, gridPos)
        self.lastValidPosition = gridPos

    def getGridSquareFromPosition(self, newPos):
        localX = newPos[0] - PartyGlobals.PartyEditorGridBounds[0][0]
        localY = newPos[2] - PartyGlobals.PartyEditorGridBounds[1][1]
        x = int(localX / PartyGlobals.PartyEditorGridSquareSize[0])
        y = int(localY / PartyGlobals.PartyEditorGridSquareSize[1])
        y = PartyGlobals.PartyEditorGridSize[1] - 1 - y
        return self.partyEditor.partyEditorGrid.getGridSquare(x, y)

    def detach(self, mouseEvent):
        taskMgr.remove('gridElementDragTask%s' % self.name)
        self.rolloverTitle.stash()
        if self.overValidSquare:
            self.partyEditor.partyEditorGrid.registerNewElement(self, self.centerGridSquare, self.getGridSize())
            self.partyEditor.updateCostsAndBank()
            self.partyEditor.handleMutuallyExclusiveActivities()
        elif self.lastValidPosition is not None:
            if self.mouseOverTrash:
                self.partyEditor.trashCanButton['state'] = DirectGuiGlobals.NORMAL
                self.lastValidPosition = None
                self.partyEditor.updateCostsAndBank()
                self.stash()
            else:
                self.setPos(render2d, self.lastValidPosition)
                self.setOverValidSquare(True)
                self.partyEditor.partyEditorGrid.registerNewElement(self, self.centerGridSquare, self.getGridSize())
                self.partyEditor.updateCostsAndBank()
                self.partyEditor.handleMutuallyExclusiveActivities()
        else:
            self.stash()
        self.checkSoldOutAndPaidStatusAndAffordability()
        return

    def placeInPartyGrounds(self, desiredXY = None):
        self.centerGridSquare = self.partyEditor.partyEditorGrid.getClearGridSquare(self.getGridSize(), desiredXY)
        if self.centerGridSquare is not None:
            self.setOverValidSquare(True)
            self.unstash()
            self.setPosHprBasedOnGridSquare(self.centerGridSquare)
            self.partyEditor.partyEditorGrid.registerNewElement(self, self.centerGridSquare, self.getGridSize())
            self.partyEditor.updateCostsAndBank()
            self.partyEditor.partyPlanner.instructionLabel['text'] = TTLocalizer.PartyPlannerEditorInstructionsPartyGrounds
            self.checkSoldOutAndPaidStatusAndAffordability()
            return True
        else:
            return False
        return

    def clicked(self, mouseEvent):
        PartyEditorGridElement.notify.debug('clicked grid element %s' % self.name)
        if self.centerGridSquare is not None:
            self.attach(mouseEvent)
            self.partyEditor.partyEditorGrid.removeElement(self.centerGridSquare, self.getGridSize())
        return

    def released(self, mouseEvent):
        PartyEditorGridElement.notify.debug('released grid element %s' % self.name)
        self.detach(mouseEvent)

    def mouseEnter(self, mouseEvent):
        parent = self.getParent()
        self.reparentTo(parent)
        self.rolloverTitle.unstash()

    def mouseExit(self, mouseEvent):
        self.rolloverTitle.stash()

    def destroy(self):
        self.unbind(DirectGuiGlobals.B1PRESS)
        self.unbind(DirectGuiGlobals.B1RELEASE)
        self.unbind(DirectGuiGlobals.ENTER)
        self.unbind(DirectGuiGlobals.EXIT)
        DirectButton.destroy(self)
Пример #8
0
class RepairPumpingGame(RepairMincroGame):
    pumpDownSounds = None
    pumpUpSounds = None
    pumpGoodSounds = None
    pumpBadSounds = None
    
    def __init__(self, repairGame):
        self.config = RepairGlobals.Pumping
        RepairMincroGame.__init__(self, repairGame, 'pumping', PLocalizer.Minigame_Repair_Pumping_Start)

    
    def _initVars(self):
        RepairMincroGame._initVars(self)
        self.pumpRate = 0.0
        self.remainingWater = 1.0
        self.chainCount = 0
        self.barDirection = UP
        self.goalIndex = TOP
        self.currentBarRate = self.config.barStartRange[0]
        self.hitRange = self.config.hitRange[0]
        self.barPercent = 0.0
        self.failedPercentAndDirection = (-1.0, UP)

    
    def _initAudio(self):
        RepairMincroGame._initAudio(self)
        if not self.pumpDownSounds:
            RepairPumpingGame.pumpDownSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_DOWN01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_DOWN02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_DOWN03))
            RepairPumpingGame.pumpUpSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_UP01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_UP02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_UP03))
            RepairPumpingGame.pumpGoodSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD03), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD04), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD05), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD06))
            RepairPumpingGame.pumpBadSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_BAD),)
        

    
    def _initVisuals(self):
        RepairMincroGame._initVisuals(self)
        self.model = loader.loadModel('models/gui/pir_m_gui_srp_pumping_main')
        self.visual = self.attachNewNode('visual')
        self.visual.setPos(-0.25, 0.0, 0.074999999999999997)
        goalTopLoc = self.model.find('**/locator_top')
        goalTopLoc.reparentTo(self.visual)
        goalBottomLoc = self.model.find('**/locator_bottom')
        goalBottomLoc.reparentTo(self.visual)
        self.goalPositions = (goalBottomLoc.getPos(self), goalTopLoc.getPos(self))
        self.greatLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Pumping_Great, text_fg = (0.20000000000000001, 0.80000000000000004, 0.29999999999999999, 1.0), text_pos = (0.0, 0.59999999999999998), text_align = TextNode.ACenter, text_font = PiratesGlobals.getPirateFont(), relief = None, text_shadow = (0.0, 0.0, 0.0, 1.0), scale = (0.080000000000000002, 0.080000000000000002, 0.080000000000000002), pos = (-0.46500000000000002, 0.0, 0.0), parent = self)
        self.failLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Pumping_Fail, text_fg = (0.80000000000000004, 0.20000000000000001, 0.29999999999999999, 1.0), text_pos = (0.0, 0.59999999999999998), text_align = TextNode.ARight, text_font = PiratesGlobals.getPirateFont(), text_shadow = (0.0, 0.0, 0.0, 1.0), relief = None, scale = (0.080000000000000002, 0.080000000000000002, 0.080000000000000002), pos = (-0.625, 0.0, 0.0), parent = self)
        self.shipBackground = self.model.find('**/static_ship_background')
        self.shipBackground.reparentTo(self.visual)
        self.waterMeter = self.model.find('**/sprite_waterBottom')
        self.waterMeter.reparentTo(self.visual)
        self.waterTop = self.model.find('**/sprite_waterTop')
        self.waterTop.reparentTo(self.visual)
        self.waterMeterTopLoc = self.waterMeter.find('**/locator_topOfShipWater')
        self.pumpBackground = self.model.find('**/pumpBackground')
        self.pumpBackground.reparentTo(self.visual)
        self.pumpWaterTop = self.model.find('**/sprite_pumpWaterTop')
        self.pumpWaterTop.reparentTo(self.visual)
        self.pumpWaterBottom = self.model.find('**/sprite_pumpWaterBottom')
        self.pumpWaterBottom.reparentTo(self.visual)
        self.pumpWaterTopLoc = self.pumpWaterBottom.find('**/locator_topOfPumpWater')
        self.pumpHandle = self.model.find('**/sprite_handle')
        self.pumpHandle.reparentTo(self.visual)
        self.pumpBar = self.model.find('**/static_pump')
        self.pumpBar.reparentTo(self.visual)
        self.goalBox = self.model.find('**/sprite_clickField')
        self.goalBox.reparentTo(self.visual)
        self.goalBox.setTransparency(1)
        self.enableGoalBox()
        self.pumpLine = self.model.find('**/sprite_bar')
        self.pumpLine.reparentTo(self.visual)
        self.ghostLine = self.visual.attachNewNode('ghostLine')
        self.pumpLine.getChild(0).copyTo(self.ghostLine)
        self.ghostLine.setScale(self.pumpLine.getScale())
        self.ghostLine.setColor(1.0, 0.20000000000000001, 0.20000000000000001, 1.0)
        self.shipForground = self.model.find('**/static_ship_foreground')
        self.shipForground.reparentTo(self.visual)
        cm = CardMaker('cardMaker')
        cm.setFrame(-0.33000000000000002, 0.33000000000000002, 0.0, 1.0)
        self.goalBox.setZ(self.goalPositions[TOP].getZ())
        self.goalBoxStartScale = self.goalBox.getSz()
        self.enableGoalBox()
        self.pumpWaterUpLerp = LerpFunc(self.setPumpWater, fromData = -0.10000000000000001, toData = 1.0, duration = 0.5)
        self.pumpWaterDownLerp = LerpFunc(self.setPumpWater, fromData = 1.0, toData = -0.10000000000000001, duration = 0.5)
        self.model.removeNode()
        del self.model

    
    def destroy(self):
        del self.goalPositions
        self.pumpBar.removeNode()
        self.pumpLine.removeNode()
        self.goalBox.removeNode()
        self.pumpHandle.removeNode()
        self.waterMeter.removeNode()
        self.waterTop.removeNode()
        self.ghostLine.removeNode()
        self.shipBackground.removeNode()
        self.shipForground.removeNode()

    
    def reset(self):
        RepairMincroGame.reset(self)
        self.remainingWater = WATER_LEVEL_START
        self.chainCount = 0
        self.barDirection = UP
        self.goalIndex = TOP
        self.barPercent = 0.0
        self.failedPercentAndDirection = (-1.0, UP)
        actualZ = self.goalPositions[BOTTOM].getZ()
        actualZ -= self.visual.getZ()
        self.pumpLine.setZ(actualZ)
        self.setGoalIndex(TOP)
        self.pumpHandle.setR(ROTATION_MIN)
        self.waterMeter.setSz(WATER_LEVEL_START)
        self.waterTop.setZ(self.waterMeterTopLoc.getZ(self.visual))
        self.ghostLine.stash()
        self.setPumpWater(1.0)
        self.failLabel.stash()
        self.greatLabel.stash()
        self.repairGame.gui.setTutorial(self.name)
        self.repairGame.gui.setTitle(self.name)

    
    def setDifficulty(self, difficulty):
        RepairMincroGame.setDifficulty(self, difficulty)
        percent = difficulty / self.repairGame.difficultyMax
        dif = self.config.pumpPowerRange[0] - self.config.pumpPowerRange[1]
        self.pumpRate = self.config.pumpPowerRange[0] - dif * percent
        dif = self.config.barStartRange[0] - self.config.barStartRange[1]
        self.currentBarRate = self.config.barStartRange[0] - dif * percent
        dif = self.config.hitRange[0] - self.config.hitRange[1]
        self.hitRange = self.config.hitRange[0] - dif * percent
        self.goalBox.setSz((self.hitRange / 0.17999999999999999) * self.goalBoxStartScale)

    
    def setGoalIndex(self, goalIndex):
        self.goalIndex = goalIndex
        self.goalBox.setZ(self, self.goalPositions[goalIndex].getZ())
        self.goalBox.setR(180 * (goalIndex - 1))

    
    def resetFail(self):
        self.failedPercentAndDirection = (-1.0, UP)
        self.enableGoalBox()
        self.hideMarkers()

    
    def updateTask(self, task):
        dt = globalClock.getDt()
        percentTimeThisStep = dt / (self.currentBarRate + self.config.barSpeedMax)
        self.barPercent = self.barPercent + percentTimeThisStep * self.barDirection
        if self.failedPercentAndDirection[0] >= 0.0:
            if self.failedPercentAndDirection[1] != self.barDirection:
                if self.failedPercentAndDirection[0] * self.barDirection < self.barPercent * self.barDirection:
                    self.resetFail()
                
            
        
        if self.barPercent >= 1.0:
            self.barPercent = 1.0
            self.barDirection = DOWN
            if not self.isLineInBox():
                self.chainCount = 0
            
            if self.failedPercentAndDirection[0] < 0.5:
                self.resetFail()
            
        elif self.barPercent <= 0.0:
            self.barPercent = 0.0
            self.barDirection = UP
            if not self.isLineInBox():
                self.chainCount = 0
            
            if self.failedPercentAndDirection[0] > 0.5:
                self.resetFail()
            
        
        actualZ = self.goalPositions[0].getZ() + (self.goalPositions[1].getZ() - self.goalPositions[0].getZ()) * self.barPercent
        actualZ -= self.visual.getZ()
        self.pumpLine.setZ(actualZ)
        return Task.cont

    
    def enableGoalBox(self):
        self.goalBox.setColor(0.20000000000000001, 1.0, 0.20000000000000001, 0.59999999999999998)
        self.goalBoxEnabled = 1

    
    def disableGoalBox(self):
        self.goalBox.setColor(1.0, 0.20000000000000001, 0.20000000000000001, 0.29999999999999999)
        self.goalBoxEnabled = 0

    
    def isLineInBox(self):
        if self.goalIndex == TOP:
            return self.barPercent >= 1.0 - self.hitRange
        else:
            return self.barPercent <= self.hitRange

    
    def onMouseClick(self):
        if self.isLineInBox() and self.goalBoxEnabled == 1:
            actualPumpAmount = self.pumpRate + self.config.chainMultiplier * self.chainCount * self.pumpRate
            actualPumpAmount *= WATER_LEVEL_START - WATER_LEVEL_DONE
            self.remainingWater -= actualPumpAmount
            self.remainingWater = max(0.0, self.remainingWater)
            self.waterMeter.setSz(self.remainingWater)
            self.waterTop.setZ(self.waterMeterTopLoc.getZ(self.visual) - 0.001)
            if self.barPercent > 0.5:
                self.pumpWaterDownLerp.duration = self.currentBarRate
                self.pumpWaterDownLerp.start()
                self.barDirection = DOWN
                self.pumpHandle.setR(ROTATION_MAX)
                random.choice(self.pumpDownSounds).play()
            else:
                self.pumpWaterUpLerp.duration = self.currentBarRate
                self.pumpWaterUpLerp.start()
                self.barDirection = UP
                self.pumpHandle.setR(ROTATION_MIN)
                random.choice(self.pumpUpSounds).play()
            if self.barPercent > 0.5:
                self.setGoalIndex(BOTTOM)
            else:
                self.setGoalIndex(TOP)
            self.currentBarRate /= self.config.barSpeedIncrease
            self.chainCount += 1
            self.setSuccessMarker()
            if self.remainingWater <= WATER_LEVEL_DONE and self.barDirection == DOWN:
                self.remainingWater = 0.0
                self.request('Outro')
                return None
            
            totalRange = WATER_LEVEL_START - WATER_LEVEL_DONE
            current = WATER_LEVEL_START - self.remainingWater
            percent = min(100, int((current / totalRange) * 100))
            self.repairGame.d_reportMincroGameProgress(percent, max(0, min(5, self.chainCount) - 1))
        else:
            self.disableGoalBox()
            self.currentBarRate /= self.config.barSpeedDecrease
            self.currentBarRate += (1 - self.config.barSpeedDecrease) * self.config.barSpeedMin
            self.currentBarRate = min(self.currentBarRate, self.config.barSpeedMin)
            self.setFailMarker()
            self.chainCount = 0
            self.failedPercentAndDirection = (self.barPercent, self.barDirection)

    
    def setPumpWater(self, value):
        self.pumpWaterBottom.setSz(value)
        self.pumpWaterTop.setZ(self.pumpWaterTopLoc.getZ(self.visual))

    
    def setSuccessMarker(self):
        self.greatLabel.setZ(self.pumpLine.getZ())
        self.greatLabel.unstash()
        pumpSoundIndex = min(len(self.pumpGoodSounds) - 1, self.chainCount / 2)
        self.pumpGoodSounds[pumpSoundIndex].play()

    
    def setFailMarker(self):
        self.hideMarkers()
        self.ghostLine.setPos(self.pumpLine.getPos())
        self.ghostLine.unstash()
        self.failLabel.setZ(self.pumpLine.getZ())
        self.failLabel.unstash()
        random.choice(self.pumpBadSounds).play()

    
    def hideMarkers(self):
        self.ghostLine.stash()
        self.greatLabel.stash()
        self.failLabel.stash()

    
    def enterGame(self):
        RepairMincroGame.enterGame(self)
        taskMgr.add(self.updateTask, 'RepairPumpingGame.updateTask')
        self.accept('mouse1', self.onMouseClick)
        self.enableGoalBox()

    
    def exitGame(self):
        RepairMincroGame.exitGame(self)
        taskMgr.remove('RepairPumpingGame.updateTask')
        self.ignore('mouse1')

    
    def enterOutro(self):
        RepairMincroGame.enterOutro(self)
        self.repairGame.d_reportMincroGameScore(150)
class RepairPumpingGame(RepairMincroGame):
    pumpDownSounds = None
    pumpUpSounds = None
    pumpGoodSounds = None
    pumpBadSounds = None
    
    def __init__(self, repairGame):
        self.config = RepairGlobals.Pumping
        RepairMincroGame.__init__(self, repairGame, 'pumping', PLocalizer.Minigame_Repair_Pumping_Start)

    
    def _initVars(self):
        RepairMincroGame._initVars(self)
        self.pumpRate = 0.0
        self.remainingWater = 1.0
        self.chainCount = 0
        self.barDirection = UP
        self.goalIndex = TOP
        self.currentBarRate = self.config.barStartRange[0]
        self.hitRange = self.config.hitRange[0]
        self.barPercent = 0.0
        self.failedPercentAndDirection = (-1.0, UP)

    
    def _initAudio(self):
        RepairMincroGame._initAudio(self)
        if not self.pumpDownSounds:
            RepairPumpingGame.pumpDownSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_DOWN01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_DOWN02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_DOWN03))
            RepairPumpingGame.pumpUpSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_UP01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_UP02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_UP03))
            RepairPumpingGame.pumpGoodSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD03), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD04), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD05), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_GOOD06))
            RepairPumpingGame.pumpBadSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_PUMP_BAD),)
        

    
    def _initVisuals(self):
        RepairMincroGame._initVisuals(self)
        self.model = loader.loadModel('models/gui/pir_m_gui_srp_pumping_main')
        self.visual = self.attachNewNode('visual')
        self.visual.setPos(-0.25, 0.0, 0.074999999999999997)
        goalTopLoc = self.model.find('**/locator_top')
        goalTopLoc.reparentTo(self.visual)
        goalBottomLoc = self.model.find('**/locator_bottom')
        goalBottomLoc.reparentTo(self.visual)
        self.goalPositions = (goalBottomLoc.getPos(self), goalTopLoc.getPos(self))
        self.greatLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Pumping_Great, text_fg = (0.20000000000000001, 0.80000000000000004, 0.29999999999999999, 1.0), text_pos = (0.0, 0.59999999999999998), text_align = TextNode.ACenter, text_font = PiratesGlobals.getPirateFont(), relief = None, text_shadow = (0.0, 0.0, 0.0, 1.0), scale = (0.080000000000000002, 0.080000000000000002, 0.080000000000000002), pos = (-0.46500000000000002, 0.0, 0.0), parent = self)
        self.failLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Pumping_Fail, text_fg = (0.80000000000000004, 0.20000000000000001, 0.29999999999999999, 1.0), text_pos = (0.0, 0.59999999999999998), text_align = TextNode.ARight, text_font = PiratesGlobals.getPirateFont(), text_shadow = (0.0, 0.0, 0.0, 1.0), relief = None, scale = (0.080000000000000002, 0.080000000000000002, 0.080000000000000002), pos = (-0.625, 0.0, 0.0), parent = self)
        self.shipBackground = self.model.find('**/static_ship_background')
        self.shipBackground.reparentTo(self.visual)
        self.waterMeter = self.model.find('**/sprite_waterBottom')
        self.waterMeter.reparentTo(self.visual)
        self.waterTop = self.model.find('**/sprite_waterTop')
        self.waterTop.reparentTo(self.visual)
        self.waterMeterTopLoc = self.waterMeter.find('**/locator_topOfShipWater')
        self.pumpBackground = self.model.find('**/pumpBackground')
        self.pumpBackground.reparentTo(self.visual)
        self.pumpWaterTop = self.model.find('**/sprite_pumpWaterTop')
        self.pumpWaterTop.reparentTo(self.visual)
        self.pumpWaterBottom = self.model.find('**/sprite_pumpWaterBottom')
        self.pumpWaterBottom.reparentTo(self.visual)
        self.pumpWaterTopLoc = self.pumpWaterBottom.find('**/locator_topOfPumpWater')
        self.pumpHandle = self.model.find('**/sprite_handle')
        self.pumpHandle.reparentTo(self.visual)
        self.pumpBar = self.model.find('**/static_pump')
        self.pumpBar.reparentTo(self.visual)
        self.goalBox = self.model.find('**/sprite_clickField')
        self.goalBox.reparentTo(self.visual)
        self.goalBox.setTransparency(1)
        self.enableGoalBox()
        self.pumpLine = self.model.find('**/sprite_bar')
        self.pumpLine.reparentTo(self.visual)
        self.ghostLine = self.visual.attachNewNode('ghostLine')
        self.pumpLine.getChild(0).copyTo(self.ghostLine)
        self.ghostLine.setScale(self.pumpLine.getScale())
        self.ghostLine.setColor(1.0, 0.20000000000000001, 0.20000000000000001, 1.0)
        self.shipForground = self.model.find('**/static_ship_foreground')
        self.shipForground.reparentTo(self.visual)
        cm = CardMaker('cardMaker')
        cm.setFrame(-0.33000000000000002, 0.33000000000000002, 0.0, 1.0)
        self.goalBox.setZ(self.goalPositions[TOP].getZ())
        self.goalBoxStartScale = self.goalBox.getSz()
        self.enableGoalBox()
        self.pumpWaterUpLerp = LerpFunc(self.setPumpWater, fromData = -0.10000000000000001, toData = 1.0, duration = 0.5)
        self.pumpWaterDownLerp = LerpFunc(self.setPumpWater, fromData = 1.0, toData = -0.10000000000000001, duration = 0.5)
        self.model.removeNode()
        del self.model

    
    def destroy(self):
        del self.goalPositions
        self.pumpBar.removeNode()
        self.pumpLine.removeNode()
        self.goalBox.removeNode()
        self.pumpHandle.removeNode()
        self.waterMeter.removeNode()
        self.waterTop.removeNode()
        self.ghostLine.removeNode()
        self.shipBackground.removeNode()
        self.shipForground.removeNode()

    
    def reset(self):
        RepairMincroGame.reset(self)
        self.remainingWater = WATER_LEVEL_START
        self.chainCount = 0
        self.barDirection = UP
        self.goalIndex = TOP
        self.barPercent = 0.0
        self.failedPercentAndDirection = (-1.0, UP)
        actualZ = self.goalPositions[BOTTOM].getZ()
        actualZ -= self.visual.getZ()
        self.pumpLine.setZ(actualZ)
        self.setGoalIndex(TOP)
        self.pumpHandle.setR(ROTATION_MIN)
        self.waterMeter.setSz(WATER_LEVEL_START)
        self.waterTop.setZ(self.waterMeterTopLoc.getZ(self.visual))
        self.ghostLine.stash()
        self.setPumpWater(1.0)
        self.failLabel.stash()
        self.greatLabel.stash()
        self.repairGame.gui.setTutorial(self.name)
        self.repairGame.gui.setTitle(self.name)

    
    def setDifficulty(self, difficulty):
        RepairMincroGame.setDifficulty(self, difficulty)
        percent = difficulty / self.repairGame.difficultyMax
        dif = self.config.pumpPowerRange[0] - self.config.pumpPowerRange[1]
        self.pumpRate = self.config.pumpPowerRange[0] - dif * percent
        dif = self.config.barStartRange[0] - self.config.barStartRange[1]
        self.currentBarRate = self.config.barStartRange[0] - dif * percent
        dif = self.config.hitRange[0] - self.config.hitRange[1]
        self.hitRange = self.config.hitRange[0] - dif * percent
        self.goalBox.setSz((self.hitRange / 0.17999999999999999) * self.goalBoxStartScale)

    
    def setGoalIndex(self, goalIndex):
        self.goalIndex = goalIndex
        self.goalBox.setZ(self, self.goalPositions[goalIndex].getZ())
        self.goalBox.setR(180 * (goalIndex - 1))

    
    def resetFail(self):
        self.failedPercentAndDirection = (-1.0, UP)
        self.enableGoalBox()
        self.hideMarkers()

    
    def updateTask(self, task):
        dt = globalClock.getDt()
        percentTimeThisStep = dt / (self.currentBarRate + self.config.barSpeedMax)
        self.barPercent = self.barPercent + percentTimeThisStep * self.barDirection
        if self.failedPercentAndDirection[0] >= 0.0:
            if self.failedPercentAndDirection[1] != self.barDirection:
                if self.failedPercentAndDirection[0] * self.barDirection < self.barPercent * self.barDirection:
                    self.resetFail()
                
            
        
        if self.barPercent >= 1.0:
            self.barPercent = 1.0
            self.barDirection = DOWN
            if not self.isLineInBox():
                self.chainCount = 0
            
            if self.failedPercentAndDirection[0] < 0.5:
                self.resetFail()
            
        elif self.barPercent <= 0.0:
            self.barPercent = 0.0
            self.barDirection = UP
            if not self.isLineInBox():
                self.chainCount = 0
            
            if self.failedPercentAndDirection[0] > 0.5:
                self.resetFail()
            
        
        actualZ = self.goalPositions[0].getZ() + (self.goalPositions[1].getZ() - self.goalPositions[0].getZ()) * self.barPercent
        actualZ -= self.visual.getZ()
        self.pumpLine.setZ(actualZ)
        return Task.cont

    
    def enableGoalBox(self):
        self.goalBox.setColor(0.20000000000000001, 1.0, 0.20000000000000001, 0.59999999999999998)
        self.goalBoxEnabled = 1

    
    def disableGoalBox(self):
        self.goalBox.setColor(1.0, 0.20000000000000001, 0.20000000000000001, 0.29999999999999999)
        self.goalBoxEnabled = 0

    
    def isLineInBox(self):
        if self.goalIndex == TOP:
            return self.barPercent >= 1.0 - self.hitRange
        else:
            return self.barPercent <= self.hitRange

    
    def onMouseClick(self):
        if self.isLineInBox() and self.goalBoxEnabled == 1:
            actualPumpAmount = self.pumpRate + self.config.chainMultiplier * self.chainCount * self.pumpRate
            actualPumpAmount *= WATER_LEVEL_START - WATER_LEVEL_DONE
            self.remainingWater -= actualPumpAmount
            self.remainingWater = max(0.0, self.remainingWater)
            self.waterMeter.setSz(self.remainingWater)
            self.waterTop.setZ(self.waterMeterTopLoc.getZ(self.visual) - 0.001)
            if self.barPercent > 0.5:
                self.pumpWaterDownLerp.duration = self.currentBarRate
                self.pumpWaterDownLerp.start()
                self.barDirection = DOWN
                self.pumpHandle.setR(ROTATION_MAX)
                random.choice(self.pumpDownSounds).play()
            else:
                self.pumpWaterUpLerp.duration = self.currentBarRate
                self.pumpWaterUpLerp.start()
                self.barDirection = UP
                self.pumpHandle.setR(ROTATION_MIN)
                random.choice(self.pumpUpSounds).play()
            if self.barPercent > 0.5:
                self.setGoalIndex(BOTTOM)
            else:
                self.setGoalIndex(TOP)
            self.currentBarRate /= self.config.barSpeedIncrease
            self.chainCount += 1
            self.setSuccessMarker()
            if self.remainingWater <= WATER_LEVEL_DONE and self.barDirection == DOWN:
                self.remainingWater = 0.0
                self.request('Outro')
                return None
            
            totalRange = WATER_LEVEL_START - WATER_LEVEL_DONE
            current = WATER_LEVEL_START - self.remainingWater
            percent = min(100, int((current / totalRange) * 100))
            self.repairGame.d_reportMincroGameProgress(percent, max(0, min(5, self.chainCount) - 1))
        else:
            self.disableGoalBox()
            self.currentBarRate /= self.config.barSpeedDecrease
            self.currentBarRate += (1 - self.config.barSpeedDecrease) * self.config.barSpeedMin
            self.currentBarRate = min(self.currentBarRate, self.config.barSpeedMin)
            self.setFailMarker()
            self.chainCount = 0
            self.failedPercentAndDirection = (self.barPercent, self.barDirection)

    
    def setPumpWater(self, value):
        self.pumpWaterBottom.setSz(value)
        self.pumpWaterTop.setZ(self.pumpWaterTopLoc.getZ(self.visual))

    
    def setSuccessMarker(self):
        self.greatLabel.setZ(self.pumpLine.getZ())
        self.greatLabel.unstash()
        pumpSoundIndex = min(len(self.pumpGoodSounds) - 1, self.chainCount / 2)
        self.pumpGoodSounds[pumpSoundIndex].play()

    
    def setFailMarker(self):
        self.hideMarkers()
        self.ghostLine.setPos(self.pumpLine.getPos())
        self.ghostLine.unstash()
        self.failLabel.setZ(self.pumpLine.getZ())
        self.failLabel.unstash()
        random.choice(self.pumpBadSounds).play()

    
    def hideMarkers(self):
        self.ghostLine.stash()
        self.greatLabel.stash()
        self.failLabel.stash()

    
    def enterGame(self):
        RepairMincroGame.enterGame(self)
        taskMgr.add(self.updateTask, 'RepairPumpingGame.updateTask')
        self.accept('mouse1', self.onMouseClick)
        self.enableGoalBox()

    
    def exitGame(self):
        RepairMincroGame.exitGame(self)
        taskMgr.remove('RepairPumpingGame.updateTask')
        self.ignore('mouse1')

    
    def enterOutro(self):
        RepairMincroGame.enterOutro(self)
        self.repairGame.d_reportMincroGameScore(150)
class RepairSawingGame(RepairMincroGame):
    sawSounds = None
    boardComplet = None
    boardDestroyed = None
    
    def __init__(self, repairGame):
        self.config = RepairGlobals.Sawing
        notify = DirectNotifyGlobal.directNotify.newCategory('RepairSawingGame')
        RepairMincroGame.__init__(self, repairGame, 'sawing', PLocalizer.Minigame_Repair_Sawing_Start)

    
    def _initVars(self):
        RepairMincroGame._initVars(self)
        self.boardsPool = { }
        self.currentBoard = None
        self.currentBoardIndex = 0
        self.onDeckBoard = None
        self.onDeckBoardIndex = 0
        self.totalScore = 0.0
        self.hitZone1Penalty = False
        self.hitZone2Penalty = False
        self.hitBoardPenalty = False
        self.moveDiffForSound = 0.0
        self.startPositions = (Point3(0.0, 0.0, 0.0),)
        self.currentStartIndex = 0
        self.lastMousePos = None
        self.board_left = None
        self.board_right = None
        self.cut = None
        self.zone1_right = None
        self.zone1_left = None
        self.zone2_right = None
        self.zone2_left = None
        self.piece1 = None
        self.piece2 = None
        self.lastHitIndex = -1
        self.sawWaypoints = []

    
    def _initAudio(self):
        RepairMincroGame._initAudio(self)
        if not self.sawSounds:
            RepairSawingGame.sawSounds = (loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT01), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT02), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT03), loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_INOUT04))
            RepairSawingGame.boardComplete = loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_COMPLETE)
            RepairSawingGame.boardDestroyed = loadSfx(SoundGlobals.SFX_MINIGAME_REPAIR_SAW_FAIL)
        

    
    def _initVisuals(self):
        RepairMincroGame._initVisuals(self)
        self.setBin('fixed', 36)
        self.model = loader.loadModel('models/gui/pir_m_gui_srp_sawing_main')
        sawModel = self.model.find('**/saw')
        sawModel.setR(193)
        sawModel.setPos(0.90000000000000002, 0.0, -0.16500000000000001)
        sawModel.setBin('gui-popup', 0)
        self.sawButton = RepairSaw(parent = self, clickDownCommand = self.sawAttachedToMouse, clickUpCommand = self.sawRemovedFromMouse, geom = sawModel, text_pos = (0.20000000000000001, -0.29999999999999999), text_fg = (1, 0, 0, 1), scale = (0.29999999999999999, 0.29999999999999999, 0.29999999999999999), relief = None, pressEffect = 0, frameSize = (-0.050000000000000003, 1.05, -0.29999999999999999, 0.050000000000000003), rolloverSound = None, clickSound = None)
        self.sawingLine = RepairSawingLine(self, self.config.sawlineLineThickness, self.config.sawlineColor, self.config.sawlineLinespawnDist)
        self.progressDescriptionLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Sawing_Description, text_fg = (1.0, 1.0, 1.0, 1.0), text_pos = (0.0, 0.0), text_shadow = (0.0, 0.0, 0.0, 1.0), text_font = PiratesGlobals.getPirateFont(), text_align = TextNode.ARight, relief = None, scale = (0.080000000000000002, 0.080000000000000002, 0.080000000000000002), pos = (-0.20000000000000001, 0.0, 0.5), parent = self)
        self.progressLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Sawing_Thresholds[3], text_fg = (1.0, 1.0, 1.0, 1.0), text_pos = (0.0, 0.0), text_shadow = (0.0, 0.0, 0.0, 1.0), text_font = PiratesGlobals.getPirateFont(), text_align = TextNode.ALeft, relief = None, scale = (0.080000000000000002, 0.080000000000000002, 0.080000000000000002), pos = (-0.17999999999999999, 0.0, 0.5), parent = self)
        self.boardDestroyedLabel = DirectLabel(text = PLocalizer.Minigame_Repair_Sawing_Board_Destroyed, text_fg = (1.0, 0.0, 0.0, 1.0), text_pos = (0.0, 0.0), text_font = PiratesGlobals.getPirateFont(), text_shadow = (0.0, 0.0, 0.0, 1.0), relief = None, scale = (0.10000000000000001, 0.10000000000000001, 0.10000000000000001), pos = (0.0, 0.0, 0.10000000000000001), parent = self)
        self.boardDestroyedLabel.setBin('fixed', 38)
        self.boardDestroyedLabel.stash()

    
    def _initIntervals(self):
        RepairMincroGame._initIntervals(self)
        self.newBoardSequence = Sequence(name = 'RepairSawingGame.newBoardSequence')
        self.splitBoardSequence = Sequence(name = 'RepairSawGame.splitBoardSequence')
        self.dropBoardSequence = Sequence(name = 'RepairSawGame.dropBoardSequence')

    
    def getNewBoard(self, boardIndex):
        board = self.model.find('**/wood%i' % boardIndex).copyTo(NodePath('board%i' % len(self.boardsPool)))
        board.reparentTo(self)
        piece1 = board.find('**/piece_1')
        piece1.setPythonTag('piece_1', self.piece1)
        piece1.setY(self.config.boardYDist)
        piece2 = board.find('**/piece_2')
        piece2.setPythonTag('piece_2', self.piece2)
        piece2.setY(self.config.boardYDist)
        pieceCut = board.find('**/piece_cut')
        pieceCut.setPythonTag('cut', self.cut)
        pieceCut.setColor(self.config.cutColor)
        pieceCut.setY(self.config.boardYDist)
        board_left = piece1.find('**/board')
        board_left.setPythonTag('left', self.board_left)
        board_right = piece2.find('**/board')
        board_right.setPythonTag('right', self.board_right)
        zone1_right = piece2.find('**/zone_1')
        zone1_right.setPythonTag('zone1_right', self.zone1_right)
        zone1_right.setColor(self.config.zone1Color)
        zone1_left = piece1.find('**/zone_1')
        zone1_left.setPythonTag('zone1_left', self.zone1_left)
        zone1_left.setColor(self.config.zone1Color)
        zone2_right = piece2.find('**/zone_2')
        zone2_right.setPythonTag('zone2_right', self.zone2_right)
        zone2_right.setColor(self.config.zone2Color)
        zone2_left = piece1.find('**/zone_2')
        zone2_left.setPythonTag('zone2_left', self.zone2_left)
        zone2_left.setColor(self.config.zone2Color)
        board.stash()
        return board

    
    def reset(self):
        for key in self.boardsPool.keys():
            board = self.boardsPool[key]
            board.removeNode()
        
        self.boardsPool.clear()
        if self.currentBoard:
            self.currentBoard.removeNode()
            self.currentBoard = None
        
        if self.onDeckBoard:
            self.onDeckBoard.removeNode()
            self.onDeckBoard = None
        
        for boardIndex in self.currentDifficultySet:
            boardIndex -= 1
            if 'copy1_%s' % boardIndex not in self.boardsPool:
                board = self.getNewBoard(boardIndex)
                self.boardsPool['copy1_%s' % boardIndex] = board
            
            if 'copy2_%s' % boardIndex not in self.boardsPool:
                board = self.getNewBoard(boardIndex)
                self.boardsPool['copy2_%s' % boardIndex] = board
                continue
        
        self.currentBoardIndex = 0
        self.currentBoard = None
        self.moveNewBoardOnDeck()
        self.onDeckBoard.unstash()
        self.totalScore = 0
        self.startPositions = (Point3(0.0, 0.0, 0.0),)
        self.sawButton.stash()
        self.sawButton.reparentTo(self)
        self.lastHitIndex = -1
        self.moveDiffForSound = 0.0
        RepairMincroGame.reset(self)
        self.repairGame.gui.setTutorial(self.name)
        self.repairGame.gui.setTitle(self.name)

    
    def destroy(self):
        RepairMincroGame.destroy(self)
        taskMgr.remove('SawingGame.updateSawTask')
        self.sawButton.destroy()
        self.sawButton.removeNode()
        del self.sawButton
        if self.currentBoard:
            self.currentBoard.removeNode()
            self.currentBoard = None
        
        if self.onDeckBoard:
            self.onDeckBoard.removeNode()
            self.onDeckBoard = None
        
        self.sawingLine = None
        self.progressDescriptionLabel.destroy()
        self.progressDescriptionLabel = None
        self.progressLabel.destroy()
        self.progressLabel = None
        self.boardDestroyedLabel.destroy()
        self.boardDestroyedLabel = None
        for key in self.boardsPool.keys():
            board = self.boardsPool[key]
            if not board.isEmpty():
                board.removeNode()
                continue
        
        self.boardsPool.clear()
        self.newBoardSequence.clearToInitial()
        del self.newBoardSequence
        self.splitBoardSequence.clearToInitial()
        del self.splitBoardSequence
        self.dropBoardSequence.clearToInitial()
        del self.dropBoardSequence

    
    def setDifficulty(self, difficulty):
        RepairMincroGame.setDifficulty(self, difficulty)
        percent = difficulty / self.repairGame.difficultyMax
        difIndex = int(math.floor(percent * (len(self.config.difficultySets) - 1)))
        self.currentDifficultySet = self.config.difficultySets[difIndex]

    
    def splitBoard(self):
        self.sawingLine.reset()
        board = self.currentBoard
        boardIndex = self.currentBoardIndex
        if self.hitZone2Penalty:
            boardSplitAnim = Parallel(LerpPosInterval(self.board_left, duration = self.config.splitBoardAnimTime, pos = Point3(-2.0, 0.0, 0.0)), LerpPosInterval(self.board_right, duration = self.config.splitBoardAnimTime, pos = Point3(2.0, 0.0, 0.0)), LerpFunc(self.zone2_left.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0), LerpFunc(self.zone2_right.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0), LerpFunc(self.zone1_left.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0), LerpFunc(self.zone1_right.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0), LerpFunc(self.cut.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0))
        elif self.hitZone1Penalty:
            boardSplitAnim = Parallel(LerpPosInterval(self.board_left, duration = self.config.splitBoardAnimTime, pos = Point3(-2.0, 0.0, 0.0)), LerpPosInterval(self.board_right, duration = self.config.splitBoardAnimTime, pos = Point3(2.0, 0.0, 0.0)), LerpPosInterval(self.zone2_left, duration = self.config.splitBoardAnimTime, pos = Point3(-2.0, 0.0, 0.0)), LerpPosInterval(self.zone2_right, duration = self.config.splitBoardAnimTime, pos = Point3(2.0, 0.0, 0.0)), LerpFunc(self.zone1_left.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0), LerpFunc(self.zone1_right.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0), LerpFunc(self.cut.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0))
        else:
            boardSplitAnim = Parallel(LerpPosInterval(self.piece1, duration = self.config.splitBoardAnimTime, pos = Point3(-2.0, self.config.boardYDist, 0.0)), LerpPosInterval(self.piece2, duration = self.config.splitBoardAnimTime, pos = Point3(2.0, self.config.boardYDist, 0.0)), LerpFunc(self.cut.setSa, duration = self.config.splitBoardAnimTime / 2.0, fromData = 1.0, toData = 0.0))
        self.splitBoardSequence = Sequence(Func(self.updateScore), Func(self.boardComplete.play), boardSplitAnim, Func(board.stash), Func(self.piece1.setPos, self.piece1.getPos()), Func(self.piece2.setPos, self.piece2.getPos()), Func(self.board_right.setPos, self.board_right.getPos()), Func(self.board_left.setPos, self.board_left.getPos()), Func(self.zone2_right.setPos, self.zone2_right.getPos()), Func(self.zone2_left.setPos, self.zone2_left.getPos()), Func(self.zone1_right.setPos, self.zone1_right.getPos()), Func(self.zone1_left.setPos, self.zone1_left.getPos()), Func(self.cut.setSa, 1.0), Func(self.zone1_right.setSa, 1.0), Func(self.zone1_left.setSa, 1.0), Func(self.zone2_right.setSa, 1.0), Func(self.zone2_left.setSa, 1.0), Func(self.board_right.setSa, 1.0), Func(self.board_left.setSa, 1.0), Func(self.loadNewBoard), Func(self.addBoardBackToPool, board, boardIndex), name = 'RepairSawGame.splitBoardSequence')
        self.splitBoardSequence.start()

    
    def dropBoard(self):
        board = self.currentBoard
        boardIndex = self.currentBoardIndex
        self.dropBoardSequence = Sequence(Parallel(Sequence(Func(self.boardDestroyedLabel.unstash), Wait(1.5), Func(self.boardDestroyedLabel.stash)), Sequence(Wait(0.5), Func(self.boardDestroyed.play), Func(self.sawingLine.reset), LerpPosInterval(board, duration = self.config.splitBoardAnimTime, pos = Point3(0.0, 0.0, -2.0)), Func(board.stash), Wait(0.5), Func(self.loadNewBoard), Func(self.addBoardBackToPool, board, boardIndex))), name = 'RepairSawGame.dropBoardSequence')
        self.dropBoardSequence.start()

    
    def addBoardBackToPool(self, board, boardIndex):
        if 'copy1_%s' % boardIndex not in self.boardsPool:
            self.boardsPool['copy1_%s' % boardIndex] = board
        elif 'copy2_%s' % boardIndex not in self.boardsPool:
            self.boardsPool['copy2_%s' % boardIndex] = board
        else:
            self.notify.error('Two copies of board type %i already in the boardsPool!' % boardIndex)

    
    def updateScoreText(self):
        self.progressLabel.unstash()
        self.progressDescriptionLabel.unstash()
        if self.hitBoardPenalty:
            self.progressLabel['text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[0]
            self.progressLabel['text_fg'] = Vec4(1.0, 0.0, 0.0, 1.0)
            self.progressLabel.setText()
        elif self.hitZone2Penalty:
            self.progressLabel['text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[1]
            self.progressLabel['text_fg'] = Vec4(1.0, 0.5, 0.0, 1.0)
            self.progressLabel.setText()
        elif self.hitZone1Penalty:
            self.progressLabel['text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[2]
            self.progressLabel['text_fg'] = Vec4(1.0, 1.0, 0.0, 1.0)
            self.progressLabel.setText()
        else:
            self.progressLabel['text'] = PLocalizer.Minigame_Repair_Sawing_Thresholds[3]
            self.progressLabel['text_fg'] = Vec4(0.0, 1.0, 0.0, 1.0)
            self.progressLabel.setText()

    
    def moveNewBoardOnDeck(self):
        boardIndex = random.randint(0, len(self.currentDifficultySet) - 1)
        boardType = self.currentDifficultySet[boardIndex]
        boardType -= 1
        if 'copy1_%s' % boardType in self.boardsPool:
            self.onDeckBoard = self.boardsPool['copy1_%s' % boardType]
            del self.boardsPool['copy1_%s' % boardType]
        elif 'copy2_%s' % boardType in self.boardsPool:
            self.onDeckBoard = self.boardsPool['copy2_%s' % boardType]
            del self.boardsPool['copy2_%s' % boardType]
        else:
            self.notify.error('No copies of board type %i in the boardsPool!' % boardType)
        self.onDeckBoardIndex = boardType
        self.onDeckBoard.setScale(0.25)
        self.onDeckBoard.setPos(0.5, -2.0, 0.56000000000000005)
        self.onDeckBoard.unstash()

    
    def loadNewBoard(self):
        self.progressLabel.stash()
        self.progressDescriptionLabel.stash()
        if self.totalScore >= self.config.totalPoints:
            if self.onDeckBoard:
                self.onDeckBoard.stash()
            
            self.progressDescriptionLabel.stash()
            taskMgr.remove('SawingGame.updateSawTask')
            self.request('Outro')
            return None
        
        self.currentBoard = self.onDeckBoard
        self.currentBoardIndex = self.onDeckBoardIndex
        self.piece1 = self.currentBoard.find('**/piece_1')
        self.piece1.setTransparency(1)
        self.piece2 = self.currentBoard.find('**/piece_2')
        self.piece2.setTransparency(1)
        self.cut = self.currentBoard.find('**/piece_cut')
        self.cut.setColor(self.config.cutColor)
        self.cut.setTransparency(1)
        self.board_left = self.piece1.find('**/board')
        self.board_left.setTransparency(1)
        self.zone1_left = self.piece1.find('**/zone_1')
        self.zone1_left.setTransparency(1)
        self.zone2_left = self.piece1.find('**/zone_2')
        self.zone2_left.setTransparency(1)
        self.board_right = self.piece2.find('**/board')
        self.board_right.setTransparency(1)
        self.zone1_right = self.piece2.find('**/zone_1')
        self.zone1_right.setTransparency(1)
        self.zone2_right = self.piece2.find('**/zone_2')
        self.zone2_right.setTransparency(1)
        self.board_left.setCollideMask(SAW_COLLIDE_MASK)
        self.board_right.setCollideMask(SAW_COLLIDE_MASK)
        self.cut.setCollideMask(SAW_COLLIDE_MASK)
        self.zone1_right.setCollideMask(SAW_COLLIDE_MASK)
        self.zone1_left.setCollideMask(SAW_COLLIDE_MASK)
        self.zone2_right.setCollideMask(SAW_COLLIDE_MASK)
        self.zone2_left.setCollideMask(SAW_COLLIDE_MASK)
        self.startPositions = (self.currentBoard.find('**/locator_start_0').getPos() + Point3(*self.config.activeBoardPosition), self.currentBoard.find('**/locator_start_1').getPos() + Point3(*self.config.activeBoardPosition))
        self.currentStartIndex = 0
        for waypoint in self.sawWaypoints:
            waypoint.removeNode()
        
        self.sawWaypoints = []
        locator = self.currentBoard.find('**/locator_0')
        index = 0
        while not locator.isEmpty():
            self.sawWaypoints.append(SawWaypoint(index, self.currentBoard, locator.getPos()))
            locator = self.currentBoard.find('**/locator_%i' % (index + 1))
            index += 1
        self.sawButton.deactivate()
        self.sawButton.setPos(self.startPositions[self.currentStartIndex])
        self.hitBoardPenalty = False
        self.hitZone1Penalty = False
        self.hitZone2Penalty = False
        self.lastMousePos = None
        self.moveDiffForSound = self.config.playSawingSoundDelta + 0.10000000000000001
        self.newBoardSequence = Sequence(Parallel(self.currentBoard.posInterval(self.config.newBoardAnimTime, Point3(*self.config.activeBoardPosition)), self.currentBoard.scaleInterval(self.config.newBoardAnimTime, 1.0)), name = 'RepairSawingGame.newBoardSequence')
        if self.state in [
            'Game']:
            self.newBoardSequence.append(Func(self.sawButton.activate))
        
        self.newBoardSequence.append(Wait(0.5))
        self.newBoardSequence.append(Func(self.moveNewBoardOnDeck))
        self.newBoardSequence.start()

    
    def updateSawTask(self, task):
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            relative = Point3(mpos.getX(), 0.0, mpos.getY())
            relative = self.getRelativePoint(render2d, relative)
            moveDiff = 0.0
            if self.lastMousePos != None:
                moveDiff = (relative - self.lastMousePos).length()
            
            pickedObjects = self.repairGame.mousePicker.getCollisions(self.currentBoard, useIntoNodePaths = True)
            self.updateWaypoints()
            if len(pickedObjects) > 0:
                self.moveDiffForSound += moveDiff
                if self.moveDiffForSound > self.config.playSawingSoundDelta:
                    sawSoundPlaying = False
                    for sound in self.sawSounds:
                        if sound.status() == 2:
                            sawSoundPlaying = True
                            break
                            continue
                    
                    if sawSoundPlaying == False:
                        sound = random.choice(self.sawSounds)
                        sound.play()
                        self.moveDiffForSound = 0.0
                    
                
                if self.board_right in pickedObjects or self.board_left in pickedObjects:
                    for waypoint in self.sawWaypoints:
                        waypoint.hit = False
                    
                    self.hitBoardPenalty = True
                    self.dropBoard()
                    self.sawButton.deactivate()
                elif self.cut in pickedObjects:
                    self.updateWaypoints()
                elif self.zone1_right in pickedObjects or self.zone1_left in pickedObjects:
                    self.updateWaypoints()
                    if self.hitZone1Penalty == False:
                        self.hitZone1Penalty = True
                    
                elif self.zone2_right in pickedObjects or self.zone2_left in pickedObjects:
                    self.updateWaypoints()
                    if self.hitZone2Penalty == False:
                        self.hitZone2Penalty = True
                    
                
                self.updateScoreText()
            else:
                boardComplete = True
                for waypoint in self.sawWaypoints:
                    if not waypoint.hit:
                        boardComplete = False
                        break
                        continue
                
                if boardComplete:
                    self.splitBoard()
                    self.sawButton.deactivate()
                
            self.lastMousePos = self.sawButton.getPos()
        
        return Task.cont

    
    def updateScore(self):
        if not self.hitBoardPenalty:
            currBoardScore = self.config.pointsPerBoard
            if not self.hitZone1Penalty:
                pass
            currBoardScore -= self.config.pointsLostForZone1 * self.hitZone2Penalty
            currBoardScore -= self.config.pointsLostForZone2 * self.hitZone2Penalty
            rating = 4 - 1 * self.hitZone2Penalty - 3 * self.hitZone1Penalty
            self.totalScore += currBoardScore
            self.totalScore = min(self.totalScore, self.config.totalPoints)
            percent = int((self.totalScore / self.config.totalPoints) * 100.0)
            self.repairGame.d_reportMincroGameProgress(percent, rating)
        

    
    def resetWaypoints(self):
        for waypoint in self.sawWaypoints:
            waypoint.hit = False
        

    
    def updateWaypoints(self):
        waypointList = self.getHitWaypoints()
        for waypointIndex in waypointList:
            self.lastHitIndex = waypointIndex
            self.sawWaypoints[waypointIndex].hit = True
            if waypointIndex == 0 and not (self.sawWaypoints[-1].hit):
                self.currentStartIndex = 0
                continue
            if waypointIndex == len(self.sawWaypoints) - 1 and not (self.sawWaypoints[0].hit):
                self.currentStartIndex = 1
                continue
        

    
    def getHitWaypoints(self):
        waypointsHit = []
        testDelta = self.config.testWaypointDelta
        for i in range(len(self.sawWaypoints)):
            waypointPos = self.sawWaypoints[i].getPos(self)
            closestDistance = 9999
            if self.lastMousePos != None:
                currMousePos = self.sawButton.getPos()
                lastMousePos = self.lastMousePos
                totalLength = (currMousePos - lastMousePos).length()
                testLength = testDelta
                while testLength < totalLength:
                    testPos = (currMousePos - lastMousePos) * (testLength / totalLength) + lastMousePos
                    self.updateSawLine(testPos)
                    testDistance = (testPos - waypointPos).length()
                    closestDistance = min(testDistance, closestDistance)
                    testLength += testDelta
            
            self.updateSawLine(self.sawButton.getPos())
            testDistance = (self.sawButton.getPos() - waypointPos).length()
            closestDistance = min(testDistance, closestDistance)
            if closestDistance < self.config.waypointRange[self.currentBoardIndex]:
                waypointsHit.append(i)
                continue
        
        return waypointsHit

    
    def updateSawLine(self, pos):
        if pos.getX() < -0.63300000000000001 or pos.getX() > 0.63300000000000001:
            self.sawingLine.reset()
            return None
        
        if pos.getZ() < -0.183 or pos.getZ() > 0.375:
            self.sawingLine.reset()
            return None
        
        self.sawingLine.update(pos)

    
    def getClosestPosition(self, positions):
        closestIndex = -1
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            relative = Point3(mpos.getX(), 0.0, mpos.getY())
            relative = self.getRelativePoint(base.a2dBackground, relative)
            bestDistance = 99999.0
            for i in range(len(positions)):
                dX = relative.getX() - positions[i].getX()
                dZ = relative.getZ() - positions[i].getZ()
                newDistance = dX * dX + dZ * dZ
                if newDistance < bestDistance:
                    bestDistance = newDistance
                    closestIndex = i
                    continue
            
        
        return closestIndex

    
    def sawAttachedToMouse(self):
        self.lastHitIndex = -1
        if not taskMgr.hasTaskNamed('SawingGame.updateSawTask'):
            taskMgr.add(self.updateSawTask, 'SawingGame.updateSawTask', priority = 2)
        

    
    def sawRemovedFromMouse(self):
        if not self.sawButton.isStashed():
            self.sawButton.setPos(self.startPositions[self.currentStartIndex])
            self.lastHitIndex = -1
            self.resetWaypoints()
            self.lastMousePos = None
            self.progressLabel.stash()
            self.progressDescriptionLabel.stash()
            self.sawingLine.reset()
            self.hitBoardPenalty = False
            self.hitZone1Penalty = False
            self.hitZone2Penalty = False
        
        taskMgr.remove('SawingGame.updateSawTask')

    
    def enterIntro(self):
        RepairMincroGame.enterIntro(self)
        self.loadNewBoard()

    
    def enterGame(self):
        RepairMincroGame.enterGame(self)
        self.repairGame.mousePicker.setCollisionMask(SAW_COLLIDE_MASK)
        self.sawButton.activate()

    
    def exitGame(self):
        RepairMincroGame.exitGame(self)
        self.sawButton.deactivate()
        self.repairGame.mousePicker.clearCollisionMask()
        taskMgr.remove('SawingGame.updateSawTask')
        self.splitBoardSequence.clearToInitial()
        self.dropBoardSequence.clearToInitial()
        localAvatar.guiMgr._showCursor()

    
    def enterOutro(self):
        RepairMincroGame.enterOutro(self)
        self.repairGame.d_reportMincroGameScore(150)
class PartyEditorGridElement(DirectButton):
    """
    PartyEditorGridElement is created when a PartyEditorListElement is created.
    They are what are placed on PartyEditorGridSquares to represent the element.
    """
    notify = directNotify.newCategory("PartyEditorGridElement")

    def __init__(self, partyEditor, id, isDecoration,
                 checkSoldOutAndPaidStatusAndAffordability, **kw):
        self.partyEditor = partyEditor
        self.id = id
        self.isDecoration = isDecoration
        self.checkSoldOutAndPaidStatusAndAffordability = checkSoldOutAndPaidStatusAndAffordability  # method
        # Change the name and the up, down, rollover, and disabled colors
        if self.isDecoration:
            self.name = TTLocalizer.PartyDecorationNameDict[self.id]["editor"]
            colorList = ((1.0, 1.0, 1.0, 1.0), (0.0, 0.0, 1.0, 1.0),
                         (0.0, 1.0, 1.0, 1.0), (0.5, 0.5, 0.5, 1.0))
            self.geom = self.partyEditor.partyPlanner.gui.find(
                "**/%s" %
                PartyGlobals.DecorationInformationDict[self.id]["gridAsset"])
        else:
            self.name = TTLocalizer.PartyActivityNameDict[self.id]["editor"]
            colorList = ((1.0, 1.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0),
                         (1.0, 1.0, 0.0, 1.0), (0.5, 0.5, 0.5, 1.0))
            self.geom = self.partyEditor.partyPlanner.gui.find(
                "**/%s" %
                PartyGlobals.ActivityInformationDict[self.id]["gridAsset"])

        optiondefs = (
            ('geom', self.geom, None),
            ('geom_scale', 1.0, None),
            ('geom_color', colorList[0], None),
            ('geom1_color', colorList[0], None),
            ('geom2_color', colorList[0], None),
            ('geom3_color', colorList[0], None),
            ('relief', None, None),
        )

        # Merge keyword options with default options, plus, this call makes
        # DirectButton work... that and the initializeoptions below... without
        # those two calls, strange... and I mean hard to debug, stuff happens.
        self.defineoptions(kw, optiondefs)
        DirectButton.__init__(self, self.partyEditor.parent)
        self.initialiseoptions(PartyEditorGridElement)
        self.setName("%sGridElement" % self.name)

        # Since normal buttons only call their command methods upon release
        # of the mouse button, we will not specify a command method and
        # instead bind our own methods to press and release.
        self.bind(DirectGuiGlobals.B1PRESS, self.clicked)
        self.bind(DirectGuiGlobals.B1RELEASE, self.released)
        self.bind(DirectGuiGlobals.ENTER, self.mouseEnter)
        self.bind(DirectGuiGlobals.EXIT, self.mouseExit)

        self.uprightNodePath = NodePath("%sUpright" % self.name)
        self.uprightNodePath.reparentTo(self)
        #debugAxis = loader.loadModel("models/misc/xyzAxis")
        #debugAxis.reparentTo(self.uprightNodePath)
        #debugAxis.setScale(0.01)
        rollOverZOffset = self.getGridSize()[1] / 30.0
        self.rolloverTitle = DirectLabel(
            relief=None,
            parent=self.uprightNodePath,
            pos=Point3(0.0, 0.0, rollOverZOffset),
            text=self.name,
            text_fg=(1.0, 1.0, 1.0, 1.0),
            text_shadow=(0.0, 0.0, 0.0, 1.0),
            text_scale=0.075,
        )
        self.rolloverTitle.stash()

        self.stash()
        self.overValidSquare = False
        self.lastValidPosition = None
        self.setColorScale(0.9, 0.9, 0.9, 0.7)
        self.setTransparency(True)
        self.mouseOverTrash = False
        self.centerGridSquare = None

    def getCorrectRotation(self):
        """
        Since the y value is inverted in our grid, we need to flip the rotation
        of elements that are to either side, but not top/bottom.
        """
        r = self.getR()
        if r == 90.0:
            r = 270.0
        elif r == 270.0:
            r = 90.0
        if self.id == PartyGlobals.ActivityIds.PartyCannon:
            return PartyUtils.convertDegreesToPartyGrid((r + 180.0))
        return PartyUtils.convertDegreesToPartyGrid(r)

    def getDecorationTuple(self, x, y):
        return (self.id, self.centerGridSquare.x,
                PartyGlobals.PartyEditorGridSize[1] - 1 -
                self.centerGridSquare.y, self.getCorrectRotation())

    def getActivityTuple(self, x, y):
        return (self.id, self.centerGridSquare.x,
                PartyGlobals.PartyEditorGridSize[1] - 1 -
                self.centerGridSquare.y, self.getCorrectRotation())

    def attach(self, mouseEvent):
        PartyEditorGridElement.notify.debug("attached grid element %s" %
                                            self.name)
        taskMgr.remove("gridElementDragTask%s" % self.name)
        vWidget2render2d = self.getPos(render2d)
        vMouse2render2d = Point3(mouseEvent.getMouse()[0], 0,
                                 mouseEvent.getMouse()[1])
        taskMgr.add(self.elementDragTask, "gridElementDragTask%s" % self.name)
        self.unstash()
        self.rolloverTitle.unstash()
        self.uprightNodePath.reparentTo(self)
        self.setPosHprToDefault()

    def elementDragTask(self, state):
        mwn = base.mouseWatcherNode
        if mwn.hasMouse():
            vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
            newPos = vMouse2render2d
            # Check to see if the new position is within the grounds bounds
            if newPos[0] > PartyGlobals.PartyEditorGridBounds[0][0] and \
               newPos[0] < PartyGlobals.PartyEditorGridBounds[1][0] and \
               newPos[2] < PartyGlobals.PartyEditorGridBounds[0][1] and \
               newPos[2] > PartyGlobals.PartyEditorGridBounds[1][1]:
                centerGridSquare = self.snapToGrid(newPos)
                if centerGridSquare is not None:
                    # The mouse is over the grid and over a valid square
                    self.centerGridSquare = centerGridSquare
                    if not self.overValidSquare:
                        self.setOverValidSquare(True)
                    if self.mouseOverTrash:
                        self.setOverTrash(False)
                    return Task.cont
            # Check to see if it is within the trash bounds
            if self.id != PartyGlobals.ActivityIds.PartyClock and \
               newPos[0] > PartyGlobals.PartyEditorTrashBounds[0][0] and \
               newPos[0] < PartyGlobals.PartyEditorTrashBounds[1][0] and \
               newPos[2] < PartyGlobals.PartyEditorTrashBounds[0][1] and \
               newPos[2] > PartyGlobals.PartyEditorTrashBounds[1][1]:
                if not self.mouseOverTrash:
                    self.setOverTrash(True)
            else:
                if self.mouseOverTrash:
                    self.setOverTrash(False)
            self.setPos(render2d, newPos)
            if self.overValidSquare:
                self.setOverValidSquare(False)
        return Task.cont

    def setOverTrash(self, value):
        self.mouseOverTrash = value
        if value:
            self.partyEditor.trashCanButton[
                "state"] = DirectGuiGlobals.DISABLED
            self.setColorScale(1.0, 0.0, 0.0, 1.0)
        else:
            self.partyEditor.trashCanButton["state"] = DirectGuiGlobals.NORMAL
            self.setColorScale(0.9, 0.9, 0.9, 0.7)

    def setOverValidSquare(self, value):
        self.overValidSquare = value
        if value:
            self.setColorScale(1.0, 1.0, 1.0, 1.0)
        else:
            self.setColorScale(0.9, 0.9, 0.9, 0.7)

    def removeFromGrid(self):
        assert self.notify.debugStateCall(self)
        if self.centerGridSquare is not None:
            self.partyEditor.partyEditorGrid.removeElement(
                self.centerGridSquare, self.getGridSize())
        self.setOverValidSquare(False)
        self.lastValidPosition = None
        self.stash()

    def snapToGrid(self, newPos):
        gridSquare = self.getGridSquareFromPosition(newPos)
        # If it's None, it's not a square that is allowed to be used
        if gridSquare == None:
            self.setPosHprToDefault()
            self.setPos(render2d, newPos)
            return None
        else:
            # We know the grid square the mouse is over is good, but is it good
            # for all the squares that our element would take up?
            if not self.partyEditor.partyEditorGrid.checkGridSquareForAvailability(
                    gridSquare, self.getGridSize()):
                # It's not available... sorry.
                self.setPos(render2d, newPos)
                return None

        self.setPosHprBasedOnGridSquare(gridSquare)
        return gridSquare

    def getGridSize(self):
        if self.isDecoration:
            return PartyGlobals.DecorationInformationDict[self.id]["gridsize"]
        else:
            return PartyGlobals.ActivityInformationDict[self.id]["gridsize"]

    def setPosHprToDefault(self):
        """Handle edge case we move the element of the edge of grid."""
        self.setR(0.0)
        self.uprightNodePath.setR(0.0)
        # assert self.notify.debug("uprightNodePathR=%s" % self.uprightNodePath.getR())

    def setPosHprBasedOnGridSquare(self, gridSquare):
        gridPos = gridSquare.getPos()
        # Move the position over if the element has any even dimensions
        if self.getGridSize()[0] % 2 == 0:
            gridPos.setX(gridPos[0] +
                         PartyGlobals.PartyEditorGridSquareSize[0] / 2.0)
        if self.getGridSize()[1] % 2 == 0:
            gridPos.setZ(gridPos[2] +
                         PartyGlobals.PartyEditorGridSquareSize[1] / 2.0)
        # Rotate the element so it always faces towards the center of the grid
        if self.id != PartyGlobals.ActivityIds.PartyFireworks:
            if gridPos[0] > PartyGlobals.PartyEditorGridCenter[
                    0] + PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(90.0)
                self.uprightNodePath.setR(-90.0)
                # assert self.notify.debug("uprightNodePathR=%s" % self.uprightNodePath.getR())
            elif gridPos[0] < PartyGlobals.PartyEditorGridCenter[
                    0] - PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(270.0)
                self.uprightNodePath.setR(-270.0)
                # assert self.notify.debug("uprightNodePathR=%s" % self.uprightNodePath.getR())
            elif gridPos[2] < PartyGlobals.PartyEditorGridCenter[
                    1] - PartyGlobals.PartyEditorGridRotateThreshold:
                self.setR(180.0)
                self.uprightNodePath.setR(-180.0)
                # assert self.notify.debug("uprightNodePathR=%s" % self.uprightNodePath.getR())
            else:
                self.setR(0.0)
                self.uprightNodePath.setR(0.0)
                # assert self.notify.debug("uprightNodePathR=%s" % self.uprightNodePath.getR())
        else:
            self.setR(270.0)
            self.uprightNodePath.setR(-270.0)
            # assert self.notify.debug("not fireworks uprightNodePathR=%s" % self.uprightNodePath.getR())

        self.setPos(render2d, gridPos)
        self.lastValidPosition = gridPos

    def getGridSquareFromPosition(self, newPos):
        localX = newPos[0] - PartyGlobals.PartyEditorGridBounds[0][0]
        localY = newPos[2] - PartyGlobals.PartyEditorGridBounds[1][1]
        x = int(localX / PartyGlobals.PartyEditorGridSquareSize[0])
        y = int(localY / PartyGlobals.PartyEditorGridSquareSize[1])
        # Reverse y value because y goes from bottom to top
        y = PartyGlobals.PartyEditorGridSize[1] - 1 - y
        return self.partyEditor.partyEditorGrid.getGridSquare(x, y)

    def detach(self, mouseEvent):
        assert PartyEditorGridElement.notify.debug("detached grid element %s" %
                                                   self.name)
        taskMgr.remove("gridElementDragTask%s" % self.name)
        self.rolloverTitle.stash()
        if self.overValidSquare:
            self.partyEditor.partyEditorGrid.registerNewElement(
                self, self.centerGridSquare, self.getGridSize())
            self.partyEditor.updateCostsAndBank()
            self.partyEditor.handleMutuallyExclusiveActivities()
        else:
            if self.lastValidPosition is not None:
                if self.mouseOverTrash:
                    self.partyEditor.trashCanButton[
                        "state"] = DirectGuiGlobals.NORMAL
                    self.lastValidPosition = None
                    self.partyEditor.updateCostsAndBank()
                    self.stash()
                else:
                    self.setPos(render2d, self.lastValidPosition)
                    self.setOverValidSquare(True)
                    self.partyEditor.partyEditorGrid.registerNewElement(
                        self, self.centerGridSquare, self.getGridSize())
                    self.partyEditor.updateCostsAndBank()
                    self.partyEditor.handleMutuallyExclusiveActivities()

            else:
                self.stash()
        self.checkSoldOutAndPaidStatusAndAffordability()

    def placeInPartyGrounds(self, desiredXY=None):
        self.centerGridSquare = self.partyEditor.partyEditorGrid.getClearGridSquare(
            self.getGridSize(), desiredXY)
        if self.centerGridSquare is not None:
            self.setOverValidSquare(True)
            self.unstash()
            self.setPosHprBasedOnGridSquare(self.centerGridSquare)
            self.partyEditor.partyEditorGrid.registerNewElement(
                self, self.centerGridSquare, self.getGridSize())
            self.partyEditor.updateCostsAndBank()
            self.partyEditor.partyPlanner.instructionLabel[
                "text"] = TTLocalizer.PartyPlannerEditorInstructionsPartyGrounds
            self.checkSoldOutAndPaidStatusAndAffordability()
            #self.uprightNodePath.wrtReparentTo(aspect2dp)
            return True
        else:
            return False

    def clicked(self, mouseEvent):
        PartyEditorGridElement.notify.debug("clicked grid element %s" %
                                            self.name)
        if self.centerGridSquare is not None:
            self.attach(mouseEvent)
            self.partyEditor.partyEditorGrid.removeElement(
                self.centerGridSquare, self.getGridSize())

    def released(self, mouseEvent):
        PartyEditorGridElement.notify.debug("released grid element %s" %
                                            self.name)
        self.detach(mouseEvent)

    def mouseEnter(self, mouseEvent):
        # make sure our text isn't obscured by other stuff in the grid
        parent = self.getParent()
        self.reparentTo(parent)
        self.rolloverTitle.unstash()

    def mouseExit(self, mouseEvent):
        self.rolloverTitle.stash()

    def destroy(self):
        self.unbind(DirectGuiGlobals.B1PRESS)
        self.unbind(DirectGuiGlobals.B1RELEASE)
        self.unbind(DirectGuiGlobals.ENTER)
        self.unbind(DirectGuiGlobals.EXIT)
        DirectButton.destroy(self)