ToonTownRewritten/toontown/quest/QuestManagerAI.py
2014-08-12 20:28:40 -04:00

397 lines
20 KiB
Python

import Quests
from direct.directnotify.DirectNotifyGlobal import directNotify
from toontown.toonbase import ToontownBattleGlobals
import random
class QuestManagerAI:
notify = directNotify.newCategory('QuestManagerAI')
def __init__(self, air):
self.air = air
def __testPercentage(self, percentage):
"""
Generate a random number and test it against the percentage chance specified.
Returns TRUE or FALSE, depending on if the generated number is within the
percentage specified.
"""
return random.randint(1, 100) <= percentage
def __incrementQuestProgress(self, quest):
"""
Increment the supplied quest's progress by 1.
"""
quest[4] += 1
def __toonQuestsList2Quests(self, quests):
return [Quests.getQuest(x[0]) for x in quests]
def removeBetaQuest(self, toon, rpcmethod):
toon.b_setWantBetaKeyQuest(0)
dg = self.air.dclassesByName['AccountAI'].aiFormatUpdate(
'BETA_KEY_QUEST', toon.DISLid, toon.DISLid,
self.air.ourChannel, 0
)
self.air.send(dg)
self.air.rpc.call(rpcmethod, webAccId=toon.getWebAccountId())
def toonKilledCogs(self, toon, suitsKilled, zoneId, activeToons):
"""
Called in battleExperience to alert the quest system that a toon has
killed some cogs.
"""
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.CogQuest):
# It's a cog quest!
for suit in suitsKilled:
# N.B.: This will iterate once if True, none if False.
# If it's a newbie quest, it will return an integer rather than a
# boolean, and will iterate x times.
for x in xrange(quest.doesCogCount(toon.getDoId(), suit, zoneId, activeToons)):
# Give us credit for this cog. If this a newbie quest, it will
# give us multiple credit(s), depending on how many newbies were
# in the battle.
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def recoverItems(self, toon, suitsKilled, zoneId):
"""
Called in battleExperience to alert the quest system that a toon should
test for recovered items.
Returns a tuple of two lists:
Index 0: a list of recovered items.
Index 1: a list of unrecovered items.
"""
recovered, notRecovered = ([] for i in xrange(2))
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.RecoverItemQuest):
isComplete = quest.getCompletionStatus(toon, toon.quests[index])
if isComplete == Quests.COMPLETE:
# This quest is complete, skip.
continue
# It's a quest where we need to recover an item!
if quest.isLocationMatch(zoneId):
# We're in the correct area to recover the item, woo!
if quest.getHolder() == Quests.Any or quest.getHolderType() in ['type', 'track', 'level']:
for suit in suitsKilled:
if quest.getCompletionStatus(toon, toon.quests[index]) == Quests.COMPLETE:
# Test if the task has already been completed.
# If it has, we don't need to iterate through the cogs anymore.
break
# Here comes the long IF statement...
if (quest.getHolder() == Quests.Any) \
or (quest.getHolderType() == 'type' and quest.getHolder() == suit['type']) \
or (quest.getHolderType() == 'track' and quest.getHolder() == suit['track']) \
or (quest.getHolderType() == 'level' and quest.getHolder() <= suit['level']):
progress = toon.quests[index][4] & pow(2, 16) - 1 # This seems to be the Disne
completion = quest.testRecover(progress)
if completion[0]:
# We win! We got the item from the cogs. :)
recovered.append(quest.getItem())
self.__incrementQuestProgress(toon.quests[index])
else:
# Tough luck, maybe next time.
notRecovered.append(quest.getItem())
toon.updateQuests()
return (recovered, notRecovered)
def toonKilledBuilding(self, toon, track, difficulty, floors, zoneId, activeToons):
"""
This method is called whenever a toon defeats a cog building.
N.B: This is called once for each toon that defeated the building.
"""
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.BuildingQuest):
# This quest is a building quest, time to see if it counts towards
# our progress!
if quest.isLocationMatch(zoneId):
# We defeated the building in the correct zone, and the building counts!
if quest.getBuildingTrack() == Quests.Any or quest.getBuildingTrack() == track:
if floors >= quest.getNumFloors():
# This building has more (or equal to) the number of floors we need.
for x in xrange(quest.doesBuildingCount(toon.getDoId(), activeToons)):
# Works the same as Cog Quests. Increment by one if it's a
# normal quest, or by the amount of newbies if it's a
# newbie quest.
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def toonKilledCogdo(self, toon, difficulty, floors, zoneId, activeToons):
pass
def toonRecoveredCogSuitPart(self, toon, zoneId, toonList):
pass
def toonDefeatedFactory(self, toon, factoryId, activeToonVictors):
"""
This method is called whenever a toon defeats a Sellbot HQ factory.
N.B: This is called once for each toon that defeated the factory.
"""
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.FactoryQuest):
# Cool, it's a factory quest! Does it count?
for x in xrange(quest.doesFactoryCount(toon.getDoId(), factoryId, activeToonVictors)):
# Woo, this counts towards our quest progress!
# Increment by the amount of credit we deserve.
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def toonDefeatedMint(self, toon, mintId, activeToonVictors):
"""
This method is called whenever a toon defeats a Cashbot HQ mint.
N.B: This is called once for each toon that defeated the mint.
"""
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.MintQuest):
# Oh lookie here, a mint quest! I love me some Polos.
for x in xrange(quest.doesMintCount(toon.getDoId(), mintId, activeToonVictors)):
# Nom nom nom nom, progress!
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def toonDefeatedStage(self, toon, stageId, activeToonVictors):
pass
def toonRodeTrolleyFirstTime(self, toon):
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.TrolleyQuest):
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def toonCalledClarabelle(self, toon):
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.PhoneQuest):
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def completeQuest(self, toon, questId):
"""
This is called whenever a toon completes a single quest in a toontask.
So far, this currently toons them up and removes the quest from their
quest list, although it may be needed for more in the future.
"""
toon.toonUp(toon.getMaxHp())
toon.removeQuest(questId)
def giveReward(self, toon, rewardId):
"""
This is called when a toon completes a whole toontask.
This grabs the reward from Quests via rewardId and issues the reward
to the toon.
"""
reward = Quests.getReward(rewardId)
if reward:
reward.sendRewardAI(toon)
def npcGiveQuest(self, npc, toon, questId, rewardId, toNpcId, storeReward=False):
"""
This is called when an NPC wants to assign a quest to the toon.
"""
rewardId = Quests.transformReward(rewardId, toon)
# If non-zero, this indicates this is the first quest in the whole ToonTask.
# This means we want to store the reward in the toons setRewardHistory.
finalReward = rewardId if storeReward else 0
progress = 0
toon.addQuest((questId, npc.getDoId(), toNpcId, rewardId, progress), finalReward)
# Tell the NPC that we assigned this quest to the given toon.
npc.assignQuest(toon.getDoId(), questId, rewardId, toNpcId)
def requestInteract(self, toonId, npc):
toon = self.air.doId2do.get(toonId)
if not toon:
# TODO: Flag suspicious. They shouldn't have got this far.
return
# Check if the toon has any quests to turn in.
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
questId, fromNpcId, toNpcId, rewardId, toonProgress = toon.quests[index]
isComplete = quest.getCompletionStatus(toon, toon.quests[index], npc)
if isComplete != Quests.COMPLETE:
# This quest isn't complete, skip.
continue
# If we're in the Toontorial, move to the next step.
if toonId in self.air.tutorialManager.avId2fsm.keys():
self.air.tutorialManager.avId2fsm[toonId].demand('Tunnel')
# Take away gags if it's a DeliverGagQuest.
if isinstance(quest, Quests.DeliverGagQuest):
track, level = quest.getGagType()
toon.inventory.setItem(track, level, toon.inventory.numItem(track, level) - quest.getNumGags())
toon.b_setInventory(toon.inventory.makeNetString())
# Check if the ToonTask has more quests to complete.
nextQuest = Quests.getNextQuest(questId, npc, toon)
if nextQuest == (Quests.NA, Quests.NA):
# No more quests in the current ToonTask!
if isinstance(quest, Quests.TrackChoiceQuest):
# TrackTrainingRewards are a little different, as we now
# have to display the gag track selection menu.
npc.presentTrackChoice(toonId, questId, quest.getChoices())
return
# This function is pretty weird... not sure why it's even here...
# But I'll include it just in case... (TMS says: "idk about this
# one, maybe a single quest can have different rewards?")
rewardId = Quests.getAvatarRewardId(toon, questId)
npc.completeQuest(toonId, questId, rewardId)
self.completeQuest(toon, questId)
self.giveReward(toon, rewardId)
return
else:
# We have another quest to go, sigh.
self.completeQuest(toon, questId)
nextQuestId = nextQuest[0]
nextRewardId = Quests.getFinalRewardId(questId, 1)
nextToNpcId = nextQuest[1]
self.npcGiveQuest(npc, toon, nextQuestId, nextRewardId, nextToNpcId)
return
# We had no quests to hand in, maybe they want to take out a new ToonTask?
if len(self.__toonQuestsList2Quests(toon.quests)) >= toon.getQuestCarryLimit():
# Nope, they already have the maximum amount of concurring quests they
# can carry. Reject them.
self.notify.debug("Rejecting toonId %d because their quest inventory is full." % toonId)
npc.rejectAvatar(toonId)
return
# Are we in the Toontorial?
if toonId in self.air.tutorialManager.avId2fsm.keys():
# Are we speaking to Tom?
if toon.getRewardHistory()[0] == 0:
self.npcGiveQuest(npc, toon, 101, Quests.findFinalRewardId(101)[0], Quests.getQuestToNpcId(101), storeReward=True) # FIXME please, i have no idea if this is correct
self.air.tutorialManager.avId2fsm[toonId].demand('Battle')
return
# Are they eligible for a tier upgrade?
tier = toon.getRewardHistory()[0]
if Quests.avatarHasAllRequiredRewards(toon, tier):
# They have all the rewards needed for the next tier.
if not Quests.avatarWorkingOnRequiredRewards(toon):
# Check to make sure they are not on the LOOPING_FINAL_TIER
if tier != Quests.LOOPING_FINAL_TIER:
tier += 1
# Set the tier
toon.b_setRewardHistory(tier, [])
else:
# They're eligible for a tier upgrade, but haven't finished all
# of their required ToonTasks yet.
self.notify.debug("Rejecting toonId %d because they are still working on their current tier." % toonId)
npc.rejectAvatarTierNotDone(toonId)
return
# Time to give them a list of "suitable" tasks!
suitableQuests = Quests.chooseBestQuests(tier, npc, toon)
if not suitableQuests:
# Uh oh! There's no suitable quests for them at the moment... reject.
self.notify.debug("Rejecting toonId %d because there are no quests available!" % toonId)
npc.rejectAvatar(toonId)
return
# Tell the NPC to select some quests from the generated list.
npc.presentQuestChoice(toonId, suitableQuests)
return
def avatarCancelled(self, toonId):
"""
This method is called by an NPCToon to tell the quest system that
a toon decided to cancel the interaction.
"""
# SECURITY TODO: Implement this!
pass
def avatarChoseQuest(self, toonId, npc, questId, rewardId, toNpcId):
"""
This method is called by an NPCToon to tell the quest system that
a toon has chosen a quest from the list supplied.
"""
toon = self.air.doId2do.get(toonId)
if not toon:
# TODO: Flag suspicious. They shouldn't have got this far.
return
self.notify.debug("toonId %d chose quest %d with rewardId %d to hand to npcId %d." % (toonId, questId, rewardId, toNpcId))
if rewardId == 5000:
# If they take out the quest, we need to tell the web server that they took it
# out and also tell the CSMUD not to issue any other toons with the quest for
# the current session (via the Account object's BETA_KEY_QUEST field).
self.removeBetaQuest(toon, 'lockBetaKey')
self.npcGiveQuest(npc, toon, questId, rewardId, toNpcId, storeReward=True)
def avatarChoseTrack(self, toonId, npc, questId, trackId):
"""
This method is called by an NPCToon to tell the quest system that
a toon has decided on the new track that they want to train for.
This is a special moment for any toon, as it determines how they
will play the game for the rest of their time at TTR. :D
"""
toon = self.air.doId2do.get(toonId)
if not toon:
# TODO: Flag suspicious. They shouldn't have got this far.
return
self.notify.debug("toonId %d chose trackId %d to train." % (toonId, trackId))
if trackId in [ToontownBattleGlobals.THROW_TRACK, ToontownBattleGlobals.SQUIRT_TRACK]:
# Better stop hackers, or our own stupidity.
self.notify.warning("toonId %s attempted to select trackId %d, which is a default track!" % (toonId, trackId))
# Yes this is suspicious, because it should be impossible.
self.air.writeServerEvent('suspicious', avId=toonId, issue='QMAI.avatarChoseTrack Attempted to train trackId %d, which is a default track!' % trackId)
return
rewardId = 401 + trackId
npc.completeQuest(toonId, questId, rewardId)
self.completeQuest(toon, questId)
self.giveReward(toon, rewardId)
def toonMadeFriend(self, toon, otherToon):
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.FriendQuest):
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
def toonFished(self, toon, zoneId):
"""
This method is called by the FishManagerAI whenever a toon catches
a fish. This checks for any "fishing" quests that are in progress,
and determines if they caught any items.
"""
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
if isinstance(quest, Quests.RecoverItemQuest):
# Well, this is a quest where we have to recover an item...
if quest.getCompletionStatus(toon, toon.quests[index]) == Quests.COMPLETE:
# Task is already complete. Check if we have any other
# quests that we need to fish for.
continue
if quest.isLocationMatch(zoneId):
# Well we're in the right zone!
if quest.getHolder() == Quests.AnyFish:
# Bazinga! This is a fishing quest!
progress = toon.quests[index][4] & pow(2, 16) - 1 # This seems to be the Disney way
completion = quest.testRecover(progress)
if completion[0]:
# We got lucky, dave! We caught the item!
self.__incrementQuestProgress(toon.quests[index])
toon.updateQuests()
# Since we caught an item already, there's no
# point in checking the other quests as we can
# only catch one item at a time via fishing.
return quest.getItem()
# Nope, no fishing quests, or we're out of luck. Too bad.
return 0
def hasTailorClothingTicket(self, toon, npc):
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
isComplete = quest.getCompletionStatus(toon, toon.quests[index], npc)
if isComplete == Quests.COMPLETE:
return True
return False
def removeClothingTicket(self, toon, npc):
for index, quest in enumerate(self.__toonQuestsList2Quests(toon.quests)):
questId, fromNpcId, toNpcId, rewardId, toonProgress = toon.quests[index]
isComplete = quest.getCompletionStatus(toon, toon.quests[index], npc)
if isComplete == Quests.COMPLETE:
toon.removeQuest(questId)
return True
return False