def check_quest_requirements(self, quest_template): # Is the player character the required race. race_is_required = quest_template.RequiredRaces > 0 if race_is_required and not (quest_template.RequiredRaces & self.player_mgr.race_mask): return False # Is the character the required class. class_is_required = quest_template.RequiredClasses > 0 if class_is_required and not (quest_template.RequiredClasses & self.player_mgr.class_mask): return False # Does the character have the required source items. source_item_required = list(filter((0).__ne__, QuestHelpers.generate_req_source_list(quest_template))) source_item_count_list = list(filter((0).__ne__, QuestHelpers.generate_req_source_count_list(quest_template))) for index, item in enumerate(source_item_required): if self.player_mgr.inventory.get_item_count(item) < source_item_count_list[index]: return False # Has the character already started the next quest in the chain. if quest_template.NextQuestInChain > 0 and quest_template.NextQuestInChain in self.completed_quests: return False # Does the character have the previous quest. if quest_template.PrevQuestId > 0 and quest_template.PrevQuestId not in self.completed_quests: return False # TODO: Does the character have the required skill return True
def still_needs_item(self, item_entry): req_item = QuestHelpers.generate_req_item_list(self.quest) required = item_entry in req_item if required: index = req_item.index(item_entry) required_items = QuestHelpers.generate_req_item_count_list( self.quest)[index] current_items = self._get_db_item_count(index) return current_items < required_items
def requires_creature_or_go(self, creature_entry): req_creature_or_go = QuestHelpers.generate_req_creature_or_go_list( self.quest) required = creature_entry in req_creature_or_go if required: index = req_creature_or_go.index(creature_entry) required_kills = QuestHelpers.generate_req_creature_or_go_count_list( self.quest)[index] current_kills = eval(f'self.db_state.mobcount{index + 1}') return current_kills < required_kills return False
def send_quest_giver_offer_reward(self, active_quest, quest_giver_guid, enable_next=True): # CGPlayer_C::OnQuestGiverChooseReward quest = active_quest.quest quest_title_bytes = PacketWriter.string_to_bytes(quest.Title) display_dialog_text = quest.OfferRewardText dialog_text_bytes = PacketWriter.string_to_bytes(display_dialog_text) data = pack( f'<QI{len(quest_title_bytes)}s{len(dialog_text_bytes)}sI', quest_giver_guid, active_quest.quest.entry, quest_title_bytes, dialog_text_bytes, 1 if enable_next else 0 # enable_next ) # Emote count, always 4. data += pack('<I', 4) for i in range(1, 5): emote = eval(f'active_quest.quest.OfferRewardEmote{i}') delay = eval(f'active_quest.quest.OfferRewardEmoteDelay{i}') data += pack('<2I', emote, delay) if active_quest.has_pick_reward(): # Reward choices rew_choice_item_list = list(filter((0).__ne__, QuestHelpers.generate_rew_choice_item_list(quest))) rew_choice_count_list = list(filter((0).__ne__, QuestHelpers.generate_rew_choice_count_list(quest))) data += pack('<I', len(rew_choice_item_list)) for index, item in enumerate(rew_choice_item_list): data += self._gen_item_struct(item, rew_choice_count_list[index]) else: data += pack('<I', 0) # Apart from available rewards to pick from, sometimes there are rewards you will also get, no matter what. if active_quest.has_item_reward(): # Required items rew_item_list = list(filter((0).__ne__, QuestHelpers.generate_rew_item_list(quest))) rew_count_list = list(filter((0).__ne__, QuestHelpers.generate_rew_count_list(quest))) data += pack('<I', len(rew_item_list)) for index, item in enumerate(rew_item_list): data += self._gen_item_struct(item, rew_count_list[index]) else: data += pack('<I', 0) # Reward data += pack( '<3I', quest.RewOrReqMoney if quest.RewOrReqMoney >= 0 else -quest.RewOrReqMoney, quest.RewSpell, quest.RewSpellCast, ) self.player_mgr.session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_QUESTGIVER_OFFER_REWARD, data))
def fill_existent_items(self): req_item = list( filter((0).__ne__, QuestHelpers.generate_req_item_list(self.quest))) req_count = list( filter((0).__ne__, QuestHelpers.generate_req_item_count_list(self.quest))) for index, item in enumerate(req_item): current_count = self.owner.inventory.get_item_count(item) if current_count: self._update_db_item_count(index, current_count, req_count[index])
def update_item_count(self, item_entry, quantity): req_items = QuestHelpers.generate_req_item_list(self.quest) req_count = QuestHelpers.generate_req_item_count_list(self.quest) req_item_index = req_items.index(item_entry) # Persist new item count. self._update_db_item_count( req_item_index, quantity, req_count[req_item_index]) # Update db memento # Notify the current item count to the player. data = pack('<2I', item_entry, quantity) packet = PacketWriter.get_packet(OpCode.SMSG_QUESTUPDATE_ADD_ITEM, data) self.owner.session.enqueue_packet(packet)
def handle_choose_reward(self, quest_giver_guid, quest_id, item_choice): if quest_id not in self.active_quests: return active_quest = self.active_quests[quest_id] if not active_quest.is_quest_complete(quest_giver_guid): return # Remove required items from the player inventory. req_item_list = QuestHelpers.generate_req_item_list(active_quest.quest) req_item_count = QuestHelpers.generate_req_item_count_list(active_quest.quest) for index, req_item in enumerate(req_item_list): if req_item != 0: self.player_mgr.inventory.remove_items(req_item, req_item_count[index]) # Add the chosen item, if any. rew_item_choice_list = QuestHelpers.generate_rew_choice_item_list(active_quest.quest) if item_choice < len(rew_item_choice_list) and rew_item_choice_list[item_choice] > 0: self.player_mgr.inventory.add_item(entry=rew_item_choice_list[item_choice], show_item_get=False) given_xp = active_quest.reward_xp() given_gold = active_quest.reward_gold() # Remove from log and mark as rewarded. self.remove_from_quest_log(quest_id) self.completed_quests.add(quest_id) # Update db quest status. active_quest.update_quest_status(rewarded=True) data = pack( '<4I', quest_id, 3, # Investigate given_xp, given_gold ) # Give player reward items, if any. Client will announce them. rew_item_list = list(filter((0).__ne__, QuestHelpers.generate_rew_item_list(active_quest.quest))) rew_item_count_list = list(filter((0).__ne__, QuestHelpers.generate_rew_count_list(active_quest.quest))) data += pack('<I', len(rew_item_list)) for index, rew_item in enumerate(rew_item_list): data += pack('<2I', rew_item_list[index], rew_item_count_list[index]) self.player_mgr.inventory.add_item(entry=rew_item_list[index], show_item_get=False) # TODO: Handle RewSpell and RewSpellCast upon completion. self.player_mgr.session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_QUESTGIVER_QUEST_COMPLETE, data)) # Update surrounding, NextQuestInChain was not working properly. self.update_surrounding_quest_status()
def pop_item(self, item_entry, count): req_item = QuestHelpers.generate_req_item_list(self.quest) required = item_entry in req_item if required: req_item_count = QuestHelpers.generate_req_item_count_list( self.quest) index = req_item.index(item_entry) current_count = self.owner.inventory.get_item_count(item_entry) if current_count - count < req_item_count[index]: self._update_db_item_count(index, -count, req_item_count[index]) self.update_quest_state(QuestState.QUEST_ACCEPTED) return True return False
def update_creature_go_count(self, creature, value): creature_go_index = QuestHelpers.generate_req_creature_or_go_list( self.quest).index(creature.entry) required = QuestHelpers.generate_req_creature_or_go_count_list( self.quest)[creature_go_index] current = self._get_db_mob_or_go_count(creature_go_index) # Current < Required is already validated on requires_creature_or_go(). self._update_db_creature_go_count(creature_go_index, 1) # Update db memento # Notify the current objective count to the player. data = pack('<4IQ', self.db_state.quest, creature.entry, current + value, required, creature.guid) packet = PacketWriter.get_packet(OpCode.SMSG_QUESTUPDATE_ADD_KILL, data) self.owner.session.enqueue_packet(packet)
def is_instant_complete_quest(self): for reqSource in QuestHelpers.generate_req_source_list(self.quest): if reqSource != 0: return False for reqItem in QuestHelpers.generate_req_item_list(self.quest): if reqItem != 0: return False for reqCreatureGo in QuestHelpers.generate_req_creature_or_go_list( self.quest): if reqCreatureGo != 0: return False for reqSpellCast in QuestHelpers.generate_req_spell_cast_list( self.quest): if reqSpellCast != 0: return False return True
def send_quest_giver_quest_details(self, quest_template, quest_giver_guid, activate_accept): # Quest information quest_title = PacketWriter.string_to_bytes(quest_template.Title) quest_details = PacketWriter.string_to_bytes(quest_template.Details) quest_objectives = PacketWriter.string_to_bytes(quest_template.Objectives) data = pack( f'<QI{len(quest_title)}s{len(quest_details)}s{len(quest_objectives)}sI', quest_giver_guid, quest_template.entry, quest_title, quest_details, quest_objectives, 1 if activate_accept else 0 ) # Reward choices rew_choice_item_list = list(filter((0).__ne__, QuestHelpers.generate_rew_choice_item_list(quest_template))) rew_choice_count_list = list(filter((0).__ne__, QuestHelpers.generate_rew_choice_count_list(quest_template))) data += pack('<I', len(rew_choice_item_list)) for index, item in enumerate(rew_choice_item_list): data += self._gen_item_struct(item, rew_choice_count_list[index]) # Reward items rew_item_list = list(filter((0).__ne__, QuestHelpers.generate_rew_item_list(quest_template))) rew_count_list = list(filter((0).__ne__, QuestHelpers.generate_rew_count_list(quest_template))) data += pack('<I', len(rew_item_list)) for index, item in enumerate(rew_item_list): data += self._gen_item_struct(item, rew_count_list[index]) # Reward money data += pack('<I', quest_template.RewOrReqMoney) # Required items req_item_list = list(filter((0).__ne__, QuestHelpers.generate_req_item_list(quest_template))) req_count_list = list(filter((0).__ne__, QuestHelpers.generate_req_item_count_list(quest_template))) data += pack('<I', len(req_item_list)) for index, item in enumerate(req_item_list): data += self._gen_item_struct(item, req_count_list[index]) # Required kill / go count req_creature_or_go_list = list(filter((0).__ne__, QuestHelpers.generate_req_creature_or_go_list(quest_template))) req_creature_or_go_count_list = list(filter((0).__ne__, QuestHelpers.generate_req_creature_or_go_count_list(quest_template))) data += pack('<I', len(req_creature_or_go_list)) for index, creature_or_go in enumerate(req_creature_or_go_list): data += pack( '<2I', creature_or_go if creature_or_go >= 0 else (creature_or_go * -1) | 0x80000000, req_creature_or_go_count_list[index] ) self.player_mgr.session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_QUESTGIVER_QUEST_DETAILS, data))
def can_complete_quest(self): if self.is_instant_complete_quest(): return True # Check for required kills / gameobjects. required_creature_go = QuestHelpers.generate_req_creature_or_go_count_list( self.quest) for i in range(0, 4): current_value = eval(f'self.db_state.mobcount{i + 1}') if current_value != required_creature_go[i]: return False # Check for required items. required_items = QuestHelpers.generate_req_item_count_list(self.quest) for i in range(0, 4): current_value = eval(f'self.db_state.itemcount{i + 1}') if current_value != required_items[i]: return False # TODO: Check ReqMoney return True
def send_quest_giver_request_items(self, active_quest, quest_giver_id, close_on_cancel): is_complete = active_quest.is_quest_complete(quest_giver_id) quest_title_bytes = PacketWriter.string_to_bytes(active_quest.quest.Title) quest = active_quest.quest if quest.RequestItemsText: dialog_text_bytes = PacketWriter.string_to_bytes(quest.RequestItemsText) else: dialog_text_bytes = PacketWriter.string_to_bytes(quest.Objectives) data = pack( f'<QI{len(quest_title_bytes)}s{len(dialog_text_bytes)}s4I', quest_giver_id, quest.entry, quest_title_bytes, dialog_text_bytes, 0, # Emote delay 0, # Emote id 1 if close_on_cancel else 0, # Close Window after cancel quest.RewOrReqMoney if quest.RewOrReqMoney >= 0 else -quest.RewOrReqMoney ) req_items = QuestHelpers.generate_req_item_list(quest) req_items_count_list = QuestHelpers.generate_req_item_count_list(quest) data += pack('<I', len(req_items)) for index in range(0, 4): if req_items[index] == 0: continue data += self._gen_item_struct(req_items[index], req_items_count_list[index]) data += pack( '<4I', 0x02, 0x03 if is_complete else 0x00, # Completable = flags1 && flags2 && flags3 && flags4 0x04, # flags2 0x08 # flags3 ) packet = PacketWriter.get_packet(OpCode.SMSG_QUESTGIVER_REQUEST_ITEMS, data) self.player_mgr.session.enqueue_packet(packet)
def get_progress(self): total_count = 0 req_creature_or_go = QuestHelpers.generate_req_creature_or_go_list( self.quest) req_creature_or_go_count = QuestHelpers.generate_req_creature_or_go_count_list( self.quest) for index, creature_or_go in enumerate(req_creature_or_go): if req_creature_or_go[index] > 0: current_count = eval(f'self.db_state.mobcount{index + 1}') required = req_creature_or_go_count[index] # Consider how many bits the previous creature required. offset = index * req_creature_or_go_count[ index - 1] if index > 0 else 0 for i in range(0, required): if i < current_count: # Turn on actual kills total_count += (1 & 1) << (1 * i) + offset else: # Fill remaining 0s (Missing kills) total_count += 0 << (1 * i) + offset # Debug, enable this to take a look on whats happening at bit level. # Logger.debug(f'{bin(mob_kills)[2:].zfill(32)}') return total_count
def _update_db_item_count(self, index, value, required_count=None): if not required_count: required_count = QuestHelpers.generate_req_item_count_list( self.quest)[index] # Be sure we clamp between 0 and required. current_db_count = self._get_db_item_count(index) if current_db_count + value > required_count: value = required_count if current_db_count + value < 0: value = 0 if index == 0: self.db_state.itemcount1 += value elif index == 1: self.db_state.itemcount2 += value elif index == 2: self.db_state.itemcount3 += value elif index == 3: self.db_state.itemcount4 += value self.save(is_new=False)
def requires_item(self, item_entry): req_item = QuestHelpers.generate_req_item_list(self.quest) req_src_item = QuestHelpers.generate_req_source_list(self.quest) return item_entry in req_item or item_entry in req_src_item
def send_quest_query_response(self, active_quest): data = pack( f'<3Ii4I', active_quest.quest.entry, active_quest.quest.Method, active_quest.quest.QuestLevel, active_quest.quest.ZoneOrSort, active_quest.quest.Type, active_quest.quest.NextQuestInChain, active_quest.quest.RewOrReqMoney, active_quest.quest.SrcItemId ) # Rewards given no matter what. rew_item_list = QuestHelpers.generate_rew_item_list(active_quest.quest) rew_item_count_list = QuestHelpers.generate_rew_count_list(active_quest.quest) for index, item in enumerate(rew_item_list): data += pack('<2I', item, rew_item_count_list[index]) # Reward choices. rew_choice_item_list = QuestHelpers.generate_rew_choice_item_list(active_quest.quest) rew_choice_count_list = QuestHelpers.generate_rew_choice_count_list(active_quest.quest) for index, item in enumerate(rew_choice_item_list): data += pack('<2I', item, rew_choice_count_list[index]) title_bytes = PacketWriter.string_to_bytes(active_quest.quest.Title) details_bytes = PacketWriter.string_to_bytes(active_quest.quest.Details) objectives_bytes = PacketWriter.string_to_bytes(active_quest.quest.Objectives) end_bytes = PacketWriter.string_to_bytes(active_quest.quest.EndText) data += pack( f'<I2fI{len(title_bytes)}s{len(details_bytes)}s{len(objectives_bytes)}s{len(end_bytes)}s', active_quest.quest.PointMapId, active_quest.quest.PointX, active_quest.quest.PointY, active_quest.quest.PointOpt, title_bytes, details_bytes, objectives_bytes, end_bytes, ) # Required kills / Required items count. req_creatures_or_gos = QuestHelpers.generate_req_creature_or_go_list(active_quest.quest) req_creatures_or_gos_count_list = QuestHelpers.generate_req_creature_or_go_count_list(active_quest.quest) req_items = QuestHelpers.generate_req_item_list(active_quest.quest) req_items_count_list = QuestHelpers.generate_req_item_count_list(active_quest.quest) for index, creature_or_go in enumerate(req_creatures_or_gos): data += pack( '<4IB', creature_or_go if creature_or_go >= 0 else (creature_or_go * -1) | 0x80000000, req_creatures_or_gos_count_list[index], req_items[index], req_items_count_list[index], 0x0 # Unknown, if missing, multiple objective quests will not display properly. ) # Objective texts. req_objective_text_list = QuestHelpers.generate_objective_text_list(active_quest.quest) for index, objective_text in enumerate(req_objective_text_list): req_objective_text_bytes = PacketWriter.string_to_bytes(req_objective_text_list[index]) data += pack( f'{len(req_objective_text_bytes)}s', req_objective_text_bytes ) self.player_mgr.session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_QUEST_QUERY_RESPONSE, data))