class QuestPrototype(object): def __init__(self, knowledge_base, quests_stack=None, created_at=None, states_to_percents=None, hero=None): self.hero = hero self.quests_stack = [] if quests_stack is None else quests_stack self.knowledge_base = knowledge_base self.machine = Machine(knowledge_base=knowledge_base, interpreter=self) self.created_at =datetime.datetime.now() if created_at is None else created_at self.states_to_percents = states_to_percents if states_to_percents is not None else {} @property def current_info(self): return self.quests_stack[-1] def serialize(self): return {'quests_stack': [info.serialize() for info in self.quests_stack], 'knowledge_base': self.knowledge_base.serialize(short=True), 'created_at': time.mktime(self.created_at.timetuple()), 'states_to_percents': self.states_to_percents,} @classmethod def deserialize(cls, data): return cls(knowledge_base=KnowledgeBase.deserialize(data['knowledge_base'], fact_classes=facts.FACTS), quests_stack=[QuestInfo.deserialize(info_data) for info_data in data['quests_stack']], created_at=datetime.datetime.fromtimestamp(data['created_at']), states_to_percents=data['states_to_percents']) @property def percents(self): return self.states_to_percents.get(self.machine.pointer.state, 0.0) @property def is_processed(self): return self.machine.is_processed def get_nearest_choice(self): return self.machine.get_nearest_choice() def make_choice(self, option_uid): choice, options, defaults = self.get_nearest_choice() if self.knowledge_base[option_uid].state_from != choice.uid: return False if not any(option.uid == option_uid for option in options): return False if not defaults[0].default: return False if not transformators.change_choice(knowledge_base=self.knowledge_base, new_option_uid=option_uid, default=False): return False self.machine.sync_pointer() if self.quests_stack: self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice()) self.hero.actions.request_replane() return True ########################################### # Object operations ########################################### def process(self): self.hero.quests.mark_updated() step_result = self.machine.do_step() if self.quests_stack: self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice()) if step_result: return self.percents return 1 def _move_hero_to(self, destination, break_at=None): from the_tale.game.actions.prototypes import ActionMoveToPrototype if self.hero.position.is_walking: self._move_hero_near(destination=None, back=True) return ActionMoveToPrototype.create(hero=self.hero, destination=destination, break_at=break_at) def _move_hero_near(self, destination, terrains=None, back=False): from the_tale.game.actions.prototypes import ActionMoveNearPlacePrototype if destination is None: destination = self.hero.position.get_nearest_dominant_place() ActionMoveNearPlacePrototype.create(hero=self.hero, place=destination, back=back, terrains=terrains) def _move_hero_on_road(self, place_from, place_to, percents): if self.hero.position.is_walking: self._move_hero_near(destination=None, back=True) return path_to_position_length = self.hero.position.get_minumum_distance_to(place_from) path_from_position_length = self.hero.position.get_minumum_distance_to(place_to) full_path_length = path_to_position_length + path_from_position_length if path_to_position_length < E: current_percents = 0.0 elif path_from_position_length < E: current_percents = 1.0 else: current_percents = path_to_position_length / full_path_length if percents <= current_percents: return path_to_pass = (percents - current_percents) * full_path_length self._move_hero_to(destination=place_to, break_at=path_to_pass / path_from_position_length) def get_current_power(self, power): return power * self.current_info.power def _give_person_power(self, hero, person, power): power = self.get_current_power(power) if power > 0: hero.places_history.add_place(person.place_id, value=person.attrs.places_help_amount) if not hero.can_change_person_power(person): return 0 power = hero.modify_politics_power(power, person=person) power += ( 1 if power > 0 else -1) * self.current_info.power_bonus person_uid = uids.person(person.id) has_profession_marker = [marker for marker in self.knowledge_base.filter(facts.ProfessionMarker) if marker.person == person_uid] if has_profession_marker: power /= len(PERSON_TYPE.records) person.cmd_change_power(hero_id=hero.id, has_place_in_preferences=hero.preferences.has_place_in_preferences(person.place), has_person_in_preferences=hero.preferences.has_person_in_preferences(person), power=power) return power def _give_place_power(self, hero, place, power): power = self.get_current_power(power) if power > 0: hero.places_history.add_place(place.id, value=1) if not hero.can_change_place_power(place): return 0 power = hero.modify_politics_power(power, place=place) place.cmd_change_power(hero_id=hero.id, has_place_in_preferences=hero.preferences.has_place_in_preferences(place), has_person_in_preferences=False, power=power) return power def _fight(self, action): from the_tale.game.actions.prototypes import ActionBattlePvE1x1Prototype if action.mob is not None: mob = mobs_storage[self.knowledge_base[action.mob].externals['id']].create_mob(self.hero, is_boss=True) else: mob = mobs_storage.get_random_mob(self.hero, mercenary=action.mercenary, is_boss=True) if mob is None: mobs_storage.get_random_mob(self.hero, is_boss=True) ActionBattlePvE1x1Prototype.create(hero=self.hero, mob=mob) def give_social_power(self, quest_results): results = {} for object_uid, result in quest_results.iteritems(): object_fact = self.knowledge_base[object_uid] if not isinstance(object_fact, facts.Person): continue person_id = object_fact.externals['id'] if person_id not in persons_storage.persons: continue results[persons_storage.persons[person_id]] = result VALUABLE_RESULTS = (QUEST_RESULTS.SUCCESSED, QUEST_RESULTS.FAILED) for (person_1, quest_1_result), (person_2, quest_2_result) in itertools.combinations(results.iteritems(), 2): if quest_1_result not in VALUABLE_RESULTS or quest_2_result not in VALUABLE_RESULTS: continue connection_type = persons_storage.social_connections.get_connection_type(person_1, person_2) if connection_type is None: continue if ( not ( (connection_type.is_PARTNER and quest_1_result == quest_2_result) or (connection_type.is_CONCURRENT and quest_1_result != quest_2_result) ) ): continue self._give_person_power(hero=self.hero, person=person_1, power=1 if quest_1_result == QUEST_RESULTS.SUCCESSED else -1) self._give_person_power(hero=self.hero, person=person_2, power=1 if quest_2_result == QUEST_RESULTS.SUCCESSED else -1) def _finish_quest(self, finish, hero): experience = self.current_info.experience experience_bonus = self.current_info.experience_bonus hero.add_experience(experience) hero.add_experience(experience_bonus, without_modifications=True) hero.statistics.change_quests_done(1) if hero.companion: hero.companion.add_experience(c.COMPANIONS_COHERENCE_EXP_PER_QUEST) for object_uid, result in finish.results.iteritems(): if result == QUEST_RESULTS.SUCCESSED: object_politic_power = 1 elif result == QUEST_RESULTS.FAILED: object_politic_power = -1 else: object_politic_power = 0 object_fact = self.knowledge_base[object_uid] if isinstance(object_fact, facts.Person): person_id = object_fact.externals['id'] if result == QUEST_RESULTS.FAILED: hero.quests.add_interfered_person(person_id) person_habits_change_source = persons_storage.persons[person_id].attrs.on_quest_habits.get(result) if person_habits_change_source: self.hero.update_habits(person_habits_change_source) self._give_person_power(self.hero, persons_storage.persons[person_id], object_politic_power) elif isinstance(object_fact, facts.Place): self._give_place_power(self.hero, places_storage.places[object_fact.externals['id']], object_politic_power) else: raise exceptions.UnknownPowerRecipientError(recipient=object_fact) for marker, default in self.current_info.used_markers.iteritems(): for change_source in HABIT_CHANGE_SOURCE.records: if change_source.quest_marker == marker and change_source.quest_default == default: self.hero.update_habits(change_source) self.give_social_power(finish.results) self.quests_stack.pop() def get_state_by_jump_pointer(self): return self.knowledge_base[self.knowledge_base[self.machine.pointer.jump].state_to] def positive_results_persons(self): finish_state = self.get_state_by_jump_pointer() for object_uid, result in finish_state.results.iteritems(): if result != QUEST_RESULTS.SUCCESSED: continue object_fact = self.knowledge_base[object_uid] if not isinstance(object_fact, facts.Person): continue person_id = object_fact.externals['id'] yield persons_storage.persons[person_id] def modify_reward_scale(self, scale): for person in self.positive_results_persons(): scale += person.attrs.on_profite_reward_bonus return scale def give_energy_on_reward(self): for person in self.positive_results_persons(): self.hero.add_energy_bonus(person.attrs.on_profite_energy) def _give_reward(self, hero, reward_type, scale): quest_info = self.current_info scale = quest_info.get_real_reward_scale(hero, scale) scale = self.modify_reward_scale(scale) self.give_energy_on_reward() # hero receive artifact if hero.can_get_artifact_for_quest(): level_delta = int(math.ceil(abs(scale))) if scale < 0: level_delta = -level_delta artifact, unequipped, sell_price = self.hero.receive_artifact(equip=False, better=False, prefered_slot=False, prefered_item=False, archetype=False, level_delta=level_delta) if artifact is not None: quest_info.process_message(knowledge_base=self.knowledge_base, hero=self.hero, message='%s_artifact' % reward_type, ext_substitution={'artifact': artifact}) return # here does not receive artifac (receive money instead) money = int(max(1, f.sell_artifact_price(hero.level) * scale)) hero.change_money(MONEY_SOURCE.EARNED_FROM_QUESTS, money) quest_info.process_message(knowledge_base=self.knowledge_base, hero=self.hero, message='%s_money' % reward_type, ext_substitution={'coins': money}) def _donothing(self, donothing_type): from the_tale.game.actions.prototypes import ActionDoNothingPrototype donothing = relations.DONOTHING_TYPE.index_value[donothing_type] writer = writers.get_writer(hero=self.hero, type=self.current_info.type, message=donothing_type, substitution={}) ActionDoNothingPrototype.create(hero=self.hero, duration=donothing.duration, messages_prefix=writer.journal_id(), messages_probability=donothing.messages_probability) @classmethod def _get_upgrdade_choice(cls, hero): choices = [relations.UPGRADE_EQUIPMENT_VARIANTS.BUY] if hero.preferences.equipment_slot and hero.equipment.get(hero.preferences.equipment_slot) is not None: choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.SHARP) if hero.equipment.get(hero.preferences.equipment_slot).can_be_broken(): choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.REPAIR) return random.choice(choices) @classmethod def upgrade_equipment_cost(cls, hero_info): return int(f.normal_action_price(hero_info.level) * ITEMS_OF_EXPENDITURE.get_quest_upgrade_equipment_fraction()) @classmethod def _upgrade_equipment(cls, process_message, hero, knowledge_base, cost): if cost is not None: cost = min(cost, hero.money) upgrade_choice = cls._get_upgrdade_choice(hero) if upgrade_choice.is_BUY: artifact, unequipped, sell_price = hero.receive_artifact(equip=True, better=True, prefered_slot=True, prefered_item=True, archetype=True) if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_ARTIFACTS, -cost) if artifact is None: process_message(knowledge_base, hero, message='upgrade__fail', ext_substitution={'coins': cost}) elif unequipped: process_message(knowledge_base, hero, message='upgrade__buy_and_change', ext_substitution={'coins': cost, 'artifact': artifact, 'unequipped': unequipped, 'sell_price': sell_price}) else: process_message(knowledge_base, hero, message='upgrade__buy', ext_substitution={'coins': cost, 'artifact': artifact}) else: if artifact is None: process_message(knowledge_base, hero, message='upgrade_free__fail', ext_substitution={}) elif unequipped: process_message(knowledge_base, hero, message='upgrade_free__buy_and_change', ext_substitution={'artifact': artifact, 'unequipped': unequipped, 'sell_price': sell_price}) else: process_message(knowledge_base, hero, message='upgrade_free__buy', ext_substitution={'artifact': artifact}) elif upgrade_choice.is_SHARP: artifact = hero.sharp_artifact() if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_SHARPENING, -cost) process_message(knowledge_base, hero, message='upgrade__sharp', ext_substitution={'coins': cost, 'artifact': artifact}) else: process_message(knowledge_base, hero, message='upgrade_free__sharp', ext_substitution={'artifact': artifact}) elif upgrade_choice.is_REPAIR: artifact = hero.repair_artifact() if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_REPAIRING, -cost) process_message(knowledge_base, hero, message='upgrade__repair', ext_substitution={'coins': cost, 'artifact': artifact}) else: process_message(knowledge_base, hero, message='upgrade_free__repair', ext_substitution={'artifact': artifact}) else: raise exceptions.UnknownUpgadeEquipmentTypeError(type=upgrade_choice) def _start_quest(self, start, hero): hero.quests.update_history(start.type, TimePrototype.get_current_turn_number()) self.quests_stack.append(QuestInfo.construct(type=start.type, uid=start.uid, knowledge_base=self.machine.knowledge_base, experience=self.get_expirience_for_quest(start.uid, hero), power=self.get_politic_power_for_quest(start.uid, hero), hero=hero)) def quest_participants(self, quest_uid): for participant in self.knowledge_base.filter(facts.QuestParticipant): if quest_uid != participant.start: continue yield participant def get_expirience_for_quest(self, quest_uid, hero): experience = f.experience_for_quest(c.QUEST_AREA_RADIUS) if hero.statistics.quests_done == 0: # since we get shortest path for first quest # and want to give exp as fast as can # and do not want to give more exp than level up required experience = int(experience/2) place_experience_bonuses = {} person_experience_bonuses = {} for participant in self.quest_participants(quest_uid): fact = self.knowledge_base[participant.participant] if isinstance(fact, facts.Person): person = persons_storage.persons.get(fact.externals['id']) person_experience_bonuses[person.id] = person.attrs.experience_bonus place = person.place elif isinstance(fact, facts.Place): place = places_storage.places.get(fact.externals['id']) place_experience_bonuses[place.id] = place.attrs.experience_bonus experience += experience * (sum(place_experience_bonuses.values()) + sum(person_experience_bonuses.values())) return experience def get_politic_power_for_quest(self, quest_uid, hero): base_politic_power = f.person_power_for_quest(c.QUEST_AREA_RADIUS) for participant in self.quest_participants(quest_uid): fact = self.knowledge_base[participant.participant] if isinstance(fact, facts.Person): person = persons_storage.persons.get(fact.externals['id']) base_politic_power += person.attrs.politic_power_bonus return base_politic_power ################################ # general callbacks ################################ def on_state__before_actions(self, state): if isinstance(state, facts.Start): self._start_quest(state, hero=self.hero) def on_state__after_actions(self, state): if isinstance(state, facts.Finish): self._finish_quest(state, hero=self.hero) def on_jump_start__before_actions(self, jump): pass def on_jump_start__after_actions(self, jump): pass def on_jump_end__before_actions(self, jump): pass def on_jump_end__after_actions(self, jump): if not isinstance(jump, facts.Option): return path = (path for path in self.knowledge_base.filter(facts.ChoicePath) if path.option == jump.uid).next() used_markers = self.current_info.used_markers for marker in jump.markers: for markers_group in questgen_relations.OPTION_MARKERS_GROUPS: if marker not in markers_group: continue for removed_marker in markers_group: if removed_marker in used_markers: del used_markers[removed_marker] used_markers[marker] = path.default ################################ # do action callbacks ################################ def do_message(self, action): self.current_info.process_message(self.knowledge_base, self.hero, action.type) def do_give_reward(self, action): self._give_reward(self.hero, action.type, action.scale) def do_fight(self, action): self._fight(action) def do_do_nothing(self, action): self._donothing(action.type) def do_upgrade_equipment(self, action): self._upgrade_equipment(self.current_info.process_message, self.hero, self.knowledge_base, cost=action.cost) def do_move_near(self, action): if action.place: self._move_hero_near(destination=places_storage.places.get(self.knowledge_base[action.place].externals['id']), terrains=action.terrains) else: self._move_hero_near(destination=None, terrains=action.terrains) ################################ # check requirements callbacks ################################ def check_located_in(self, requirement): object_fact = self.knowledge_base[requirement.object] place_fact = self.knowledge_base[requirement.place] place = places_storage.places[place_fact.externals['id']] if isinstance(object_fact, facts.Person): person = persons_storage.persons[object_fact.externals['id']] return person.place.id == place.id if isinstance(object_fact, facts.Hero): return bool(self.hero.id == object_fact.externals['id'] and self.hero.position.place and self.hero.position.place.id == place.id) raise exceptions.UnknownRequirementError(requirement=requirement) def check_located_near(self, requirement): object_fact = self.knowledge_base[requirement.object] place_fact = self.knowledge_base[requirement.place] place = places_storage.places[place_fact.externals['id']] if isinstance(object_fact, facts.Person): return False if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False if self.hero.position.place: return False hero_place = self.hero.position.get_nearest_dominant_place() return place.id == hero_place.id raise exceptions.UnknownRequirementError(requirement=requirement) def check_located_on_road(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return False if not isinstance(object_fact, facts.Hero): raise exceptions.UnknownRequirementError(requirement=requirement) if self.hero.id != object_fact.externals['id']: return False place_from = places_storage.places[self.knowledge_base[requirement.place_from].externals['id']] place_to = places_storage.places[self.knowledge_base[requirement.place_to].externals['id']] percents = requirement.percents path_to_position_length = self.hero.position.get_minumum_distance_to(place_from) path_from_position_length = self.hero.position.get_minumum_distance_to(place_to) full_path_length = path_to_position_length + path_from_position_length if path_to_position_length < E: current_percents = 0.0 elif path_from_position_length < E: current_percents = 1.0 else: current_percents = path_to_position_length / full_path_length return current_percents >= percents def check_has_money(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return False if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False return self.hero.money >= requirement.money raise exceptions.UnknownRequirementError(requirement=requirement) def check_is_alive(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return True if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False return self.hero.is_alive raise exceptions.UnknownRequirementError(requirement=requirement) ################################ # satisfy requirements callbacks ################################ def satisfy_located_in(self, requirement): from . import logic object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): person_uid = object_fact.uid person = persons_storage.persons[object_fact.externals['id']] new_place_uid= uids.place(person.place.id) if new_place_uid not in self.knowledge_base: self.knowledge_base += logic.fact_place(person.place) # переписываем все ограничения в базе for fact in self.knowledge_base.filter(facts.State): for state_requirement in fact.require: if isinstance(state_requirement, requirements.LocatedIn) and state_requirement.object == person_uid: state_requirement.place = new_place_uid return if not isinstance(object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) self._move_hero_to(destination=places_storage.places[self.knowledge_base[requirement.place].externals['id']]) def satisfy_located_near(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance(object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) if requirement.place is None: self._move_hero_near(destination=None, terrains=requirement.terrains) else: self._move_hero_near(destination=places_storage.places.get(self.knowledge_base[requirement.place].externals['id']), terrains=requirement.terrains) def satisfy_located_on_road(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance(object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) self._move_hero_on_road(place_from=places_storage.places[self.knowledge_base[requirement.place_from].externals['id']], place_to=places_storage.places[self.knowledge_base[requirement.place_to].externals['id']], percents=requirement.percents) def satisfy_has_money(self, requirement): raise exceptions.UnknownRequirementError(requirement=requirement) def satisfy_is_alive(self, requirement): raise exceptions.UnknownRequirementError(requirement=requirement) ################################ # ui info ################################ def ui_info(self): return {'line': [info.ui_info(self.hero) for info in self.quests_stack]} @classmethod def no_quests_ui_info(cls, in_place): if in_place: return {'line': [NO_QUEST_INFO__IN_PLACE.ui_info(None)]} else: return {'line': [NO_QUEST_INFO__OUT_PLACE.ui_info(None)]} @classmethod def next_spending_ui_info(cls, spending): NEXT_SPENDING_INFO = QuestInfo(type='next-spending', uid='next-spending', name=u'Накопить золото', action=u'копит', choice=None, choice_alternatives=(), experience=0, power=0, experience_bonus=0, power_bonus=0, actors={'goal': (spending, u'цель')}, used_markers=set()) return {'line': [NEXT_SPENDING_INFO.ui_info(None)]}
class QuestPrototype(object): def __init__(self, knowledge_base, quests_stack=None, created_at=None, states_to_percents=None, hero=None): self.hero = hero self.quests_stack = [] if quests_stack is None else quests_stack self.knowledge_base = knowledge_base self.machine = Machine(knowledge_base=knowledge_base, interpreter=self) self.created_at = datetime.datetime.now( ) if created_at is None else created_at self.states_to_percents = states_to_percents if states_to_percents is not None else {} @property def current_info(self): return self.quests_stack[-1] def serialize(self): return { 'quests_stack': [info.serialize() for info in self.quests_stack], 'knowledge_base': self.knowledge_base.serialize(short=True), 'created_at': time.mktime(self.created_at.timetuple()), 'states_to_percents': self.states_to_percents, } @classmethod def deserialize(cls, data): return cls( knowledge_base=KnowledgeBase.deserialize(data['knowledge_base'], fact_classes=facts.FACTS), quests_stack=[ QuestInfo.deserialize(info_data) for info_data in data['quests_stack'] ], created_at=datetime.datetime.fromtimestamp(data['created_at']), states_to_percents=data['states_to_percents']) @property def percents(self): return self.states_to_percents.get(self.machine.pointer.state, 0.0) @property def is_processed(self): return self.machine.is_processed def get_nearest_choice(self): return self.machine.get_nearest_choice() def make_choice(self, option_uid): choice, options, defaults = self.get_nearest_choice() if self.knowledge_base[option_uid].state_from != choice.uid: return False if not any(option.uid == option_uid for option in options): return False if not defaults[0].default: return False if not transformators.change_choice(knowledge_base=self.knowledge_base, new_option_uid=option_uid, default=False): return False self.machine.sync_pointer() if self.quests_stack: self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice()) self.hero.actions.request_replane() return True ########################################### # Object operations ########################################### def process(self): self.hero.quests.mark_updated() step_result = self.machine.do_step() if self.quests_stack: self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice()) if step_result: return self.percents return 1 def _move_hero_to(self, destination, break_at=None): from the_tale.game.actions.prototypes import ActionMoveToPrototype if self.hero.position.is_walking: self._move_hero_near(destination=None, back=True) return ActionMoveToPrototype.create(hero=self.hero, destination=destination, break_at=break_at) def _move_hero_near(self, destination, terrains=None, back=False): from the_tale.game.actions.prototypes import ActionMoveNearPlacePrototype if destination is None: destination = self.hero.position.get_nearest_dominant_place() ActionMoveNearPlacePrototype.create(hero=self.hero, place=destination, back=back, terrains=terrains) def _move_hero_on_road(self, place_from, place_to, percents): if self.hero.position.is_walking: self._move_hero_near(destination=None, back=True) return path_to_position_length = self.hero.position.get_minumum_distance_to( place_from) path_from_position_length = self.hero.position.get_minumum_distance_to( place_to) full_path_length = path_to_position_length + path_from_position_length if path_to_position_length < E: current_percents = 0.0 elif path_from_position_length < E: current_percents = 1.0 else: current_percents = path_to_position_length / full_path_length if percents <= current_percents: return path_to_pass = (percents - current_percents) * full_path_length self._move_hero_to(destination=place_to, break_at=path_to_pass / path_from_position_length) def get_current_power(self, power): return power * self.current_info.power def modify_person_power(self, power, person): power += (1 if power > 0 else -1) * self.current_info.power_bonus person_uid = uids.person(person.id) has_profession_marker = [ marker for marker in self.knowledge_base.filter(facts.ProfessionMarker) if marker.person == person_uid ] if has_profession_marker: power /= len(PERSON_TYPE.records) return power def _fight(self, action): from the_tale.game.actions.prototypes import ActionBattlePvE1x1Prototype if action.mob is not None: mob = mobs_storage.mobs[self.knowledge_base[ action.mob].externals['id']].create_mob(self.hero, is_boss=True) else: mob = mobs_storage.mobs.get_random_mob(self.hero, mercenary=action.mercenary, is_boss=True) if mob is None: mobs_storage.mobs.get_random_mob(self.hero, is_boss=True) ActionBattlePvE1x1Prototype.create(hero=self.hero, mob=mob) def _finish_quest(self, finish, hero): experience = self.current_info.experience experience_bonus = self.current_info.experience_bonus hero.add_experience(experience) hero.add_experience(experience_bonus, without_modifications=True) hero.statistics.change_quests_done(1) if hero.companion: hero.companion.add_experience(c.COMPANIONS_COHERENCE_EXP_PER_QUEST) power_impacts = [] for object_uid, result in finish.results.items(): object_fact = self.knowledge_base[object_uid] if isinstance(object_fact, facts.Person): person_id = object_fact.externals['id'] if result == QUEST_RESULTS.FAILED: hero.quests.add_interfered_person(person_id) person_habits_change_source = persons_storage.persons[ person_id].attrs.on_quest_habits.get(result) if person_habits_change_source: self.hero.update_habits(person_habits_change_source) power = self.finish_quest_person_power(result, object_uid) power_impacts.extend( persons_logic.impacts_from_hero( self.hero, persons_storage.persons[person_id], power)) elif isinstance(object_fact, facts.Place): power = self.finish_quest_place_power(result, object_uid) power_impacts.extend( places_logic.impacts_from_hero( self.hero, places_storage.places[object_fact.externals['id']], power)) else: raise exceptions.UnknownPowerRecipientError( recipient=object_fact) politic_power_logic.add_power_impacts(power_impacts) for marker, default in self.current_info.used_markers.items(): for change_source in HABIT_CHANGE_SOURCE.records: if change_source.quest_marker == marker and change_source.quest_default == default: self.hero.update_habits(change_source) self.quests_stack.pop() def finish_quest_power(self, result): if result == QUEST_RESULTS.SUCCESSED: object_politic_power = 1 elif result == QUEST_RESULTS.FAILED: object_politic_power = -1 else: object_politic_power = 0 object_politic_power = self.get_current_power(object_politic_power) power_bonus = 0 if result != QUEST_RESULTS.NEUTRAL: power_bonus = (1 if object_politic_power > 0 else -1) * self.current_info.power_bonus return object_politic_power, power_bonus def finish_quest_person_power(self, result, object_uid): object_politic_power, power_bonus = self.finish_quest_power(result) has_profession_marker = [ marker for marker in self.knowledge_base.filter(facts.ProfessionMarker) if marker.person == object_uid ] if has_profession_marker: object_politic_power /= len(PERSON_TYPE.records) return object_politic_power + power_bonus def finish_quest_place_power(self, result, object_uid): object_politic_power, power_bonus = self.finish_quest_power(result) return object_politic_power + power_bonus def get_state_by_jump_pointer(self): return self.knowledge_base[self.knowledge_base[ self.machine.pointer.jump].state_to] def positive_results_persons(self): finish_state = self.get_state_by_jump_pointer() for object_uid, result in finish_state.results.items(): if result != QUEST_RESULTS.SUCCESSED: continue object_fact = self.knowledge_base[object_uid] if not isinstance(object_fact, facts.Person): continue person_id = object_fact.externals['id'] yield persons_storage.persons[person_id] def modify_reward_scale(self, scale): for person in self.positive_results_persons(): scale += person.attrs.on_profite_reward_bonus return scale def give_energy_on_reward(self): if not self.hero.can_regenerate_energy: return energy = sum(person.attrs.on_profite_energy for person in self.positive_results_persons()) if energy == 0: return tt_api_energy.change_energy_balance(account_id=self.hero.account_id, type='for_quest', energy=energy, async=True, autocommit=True) def _give_reward(self, hero, reward_type, scale): quest_info = self.current_info scale = quest_info.get_real_reward_scale(hero, scale) scale = self.modify_reward_scale(scale) self.give_energy_on_reward() # hero receive artifact if hero.can_get_artifact_for_quest(): level_delta = int(math.ceil(abs(scale))) if scale < 0: level_delta = -level_delta artifact, unequipped, sell_price = self.hero.receive_artifact( equip=False, better=False, prefered_slot=False, prefered_item=False, archetype=True, level_delta=level_delta) if artifact is not None: quest_info.process_message( knowledge_base=self.knowledge_base, hero=self.hero, message='%s_artifact' % reward_type, ext_substitution={'artifact': artifact}) return # here does not receive artifac (receive money instead) money = int(max(1, f.sell_artifact_price(hero.level) * scale)) hero.change_money(MONEY_SOURCE.EARNED_FROM_QUESTS, money) quest_info.process_message(knowledge_base=self.knowledge_base, hero=self.hero, message='%s_money' % reward_type, ext_substitution={'coins': money}) def _donothing(self, donothing_type): from the_tale.game.actions.prototypes import ActionDoNothingPrototype donothing = relations.DONOTHING_TYPE.index_value[donothing_type] writer = writers.get_writer(hero=self.hero, type=self.current_info.type, message=donothing_type, substitution={}) ActionDoNothingPrototype.create( hero=self.hero, duration=donothing.duration, messages_prefix=writer.journal_id(), messages_probability=donothing.messages_probability) @classmethod def _get_upgrdade_choice(cls, hero): choices = [relations.UPGRADE_EQUIPMENT_VARIANTS.BUY] if hero.preferences.equipment_slot and hero.equipment.get( hero.preferences.equipment_slot) is not None: choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.SHARP) if hero.equipment.get( hero.preferences.equipment_slot).can_be_broken(): choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.REPAIR) return random.choice(choices) @classmethod def upgrade_equipment_cost(cls, hero_info): return int( f.normal_action_price(hero_info.level) * ITEMS_OF_EXPENDITURE.get_quest_upgrade_equipment_fraction()) @classmethod def _upgrade_equipment(cls, process_message, hero, knowledge_base, cost): if cost is not None: cost = min(cost, hero.money) upgrade_choice = cls._get_upgrdade_choice(hero) if upgrade_choice.is_BUY: artifact, unequipped, sell_price = hero.receive_artifact( equip=True, better=True, prefered_slot=True, prefered_item=True, archetype=True) if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_ARTIFACTS, -cost) if artifact is None: process_message(knowledge_base, hero, message='upgrade__fail', ext_substitution={'coins': cost}) elif unequipped: process_message(knowledge_base, hero, message='upgrade__buy_and_change', ext_substitution={ 'coins': cost, 'artifact': artifact, 'unequipped': unequipped, 'sell_price': sell_price }) else: process_message(knowledge_base, hero, message='upgrade__buy', ext_substitution={ 'coins': cost, 'artifact': artifact }) else: if artifact is None: process_message(knowledge_base, hero, message='upgrade_free__fail', ext_substitution={}) elif unequipped: process_message(knowledge_base, hero, message='upgrade_free__buy_and_change', ext_substitution={ 'artifact': artifact, 'unequipped': unequipped, 'sell_price': sell_price }) else: process_message(knowledge_base, hero, message='upgrade_free__buy', ext_substitution={'artifact': artifact}) elif upgrade_choice.is_SHARP: artifact = hero.sharp_artifact() if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_SHARPENING, -cost) process_message(knowledge_base, hero, message='upgrade__sharp', ext_substitution={ 'coins': cost, 'artifact': artifact }) else: process_message(knowledge_base, hero, message='upgrade_free__sharp', ext_substitution={'artifact': artifact}) elif upgrade_choice.is_REPAIR: artifact = hero.repair_artifact() if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_REPAIRING, -cost) process_message(knowledge_base, hero, message='upgrade__repair', ext_substitution={ 'coins': cost, 'artifact': artifact }) else: process_message(knowledge_base, hero, message='upgrade_free__repair', ext_substitution={'artifact': artifact}) else: raise exceptions.UnknownUpgadeEquipmentTypeError( type=upgrade_choice) def _start_quest(self, start, hero): hero.quests.update_history(start.type, turn.number()) self.quests_stack.append( QuestInfo.construct( type=start.type, uid=start.uid, knowledge_base=self.machine.knowledge_base, experience=self.get_expirience_for_quest(start.uid, hero), power=self.get_politic_power_for_quest(start.uid, hero), hero=hero)) def quest_participants(self, quest_uid): for participant in self.knowledge_base.filter(facts.QuestParticipant): if quest_uid != participant.start: continue yield participant def get_expirience_for_quest(self, quest_uid, hero): experience = f.experience_for_quest(c.QUEST_AREA_RADIUS) if hero.statistics.quests_done == 0: # since we get shortest path for first quest # and want to give exp as fast as can # and do not want to give more exp than level up required experience = int(experience / 2) place_experience_bonuses = {} person_experience_bonuses = {} for participant in self.quest_participants(quest_uid): fact = self.knowledge_base[participant.participant] if isinstance(fact, facts.Person): person = persons_storage.persons.get(fact.externals['id']) person_experience_bonuses[ person.id] = person.attrs.experience_bonus place = person.place elif isinstance(fact, facts.Place): place = places_storage.places.get(fact.externals['id']) place_experience_bonuses[place.id] = place.attrs.experience_bonus experience += experience * (sum(place_experience_bonuses.values()) + sum(person_experience_bonuses.values())) return experience def get_politic_power_for_quest(self, quest_uid, hero): base_politic_power = f.person_power_for_quest(c.QUEST_AREA_RADIUS) for participant in self.quest_participants(quest_uid): fact = self.knowledge_base[participant.participant] if isinstance(fact, facts.Person): person = persons_storage.persons.get(fact.externals['id']) base_politic_power += person.attrs.politic_power_bonus return base_politic_power ################################ # general callbacks ################################ def on_state__before_actions(self, state): if isinstance(state, facts.Start): self._start_quest(state, hero=self.hero) def on_state__after_actions(self, state): if isinstance(state, facts.Finish): self._finish_quest(state, hero=self.hero) def on_jump_start__before_actions(self, jump): pass def on_jump_start__after_actions(self, jump): pass def on_jump_end__before_actions(self, jump): pass def on_jump_end__after_actions(self, jump): if not isinstance(jump, facts.Option): return path = next((path for path in self.knowledge_base.filter(facts.ChoicePath) if path.option == jump.uid)) used_markers = self.current_info.used_markers for marker in jump.markers: for markers_group in questgen_relations.OPTION_MARKERS_GROUPS: if marker not in markers_group: continue for removed_marker in markers_group: if removed_marker in used_markers: del used_markers[removed_marker] used_markers[marker] = path.default ################################ # do action callbacks ################################ def do_message(self, action): self.current_info.process_message(self.knowledge_base, self.hero, action.type) def do_give_reward(self, action): self._give_reward(self.hero, action.type, action.scale) def do_fight(self, action): self._fight(action) def do_do_nothing(self, action): self._donothing(action.type) def do_upgrade_equipment(self, action): self._upgrade_equipment(self.current_info.process_message, self.hero, self.knowledge_base, cost=action.cost) def do_move_near(self, action): if action.place: self._move_hero_near(destination=places_storage.places.get( self.knowledge_base[action.place].externals['id']), terrains=action.terrains) else: self._move_hero_near(destination=None, terrains=action.terrains) ################################ # check requirements callbacks ################################ def check_located_in(self, requirement): object_fact = self.knowledge_base[requirement.object] place_fact = self.knowledge_base[requirement.place] place = places_storage.places[place_fact.externals['id']] if isinstance(object_fact, facts.Person): person = persons_storage.persons[object_fact.externals['id']] return person.place.id == place.id if isinstance(object_fact, facts.Hero): return bool(self.hero.id == object_fact.externals['id'] and self.hero.position.place and self.hero.position.place.id == place.id) raise exceptions.UnknownRequirementError(requirement=requirement) def check_located_near(self, requirement): object_fact = self.knowledge_base[requirement.object] place_fact = self.knowledge_base[requirement.place] place = places_storage.places[place_fact.externals['id']] if isinstance(object_fact, facts.Person): return False if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False # если городу принадлежит только одна клетка, нак которой он находится, # то прогулкой в его окрестностях считается и нахождение в нём самом if self.hero.position.place and len( self.hero.position.place.nearest_cells) > 1: return False hero_place = self.hero.position.get_nearest_dominant_place() return place.id == hero_place.id raise exceptions.UnknownRequirementError(requirement=requirement) def check_located_on_road(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return False if not isinstance(object_fact, facts.Hero): raise exceptions.UnknownRequirementError(requirement=requirement) if self.hero.id != object_fact.externals['id']: return False place_from = places_storage.places[self.knowledge_base[ requirement.place_from].externals['id']] place_to = places_storage.places[self.knowledge_base[ requirement.place_to].externals['id']] percents = requirement.percents path_to_position_length = self.hero.position.get_minumum_distance_to( place_from) path_from_position_length = self.hero.position.get_minumum_distance_to( place_to) full_path_length = path_to_position_length + path_from_position_length if path_to_position_length < E: current_percents = 0.0 elif path_from_position_length < E: current_percents = 1.0 else: current_percents = path_to_position_length / full_path_length return current_percents >= percents def check_has_money(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return False if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False return self.hero.money >= requirement.money raise exceptions.UnknownRequirementError(requirement=requirement) def check_is_alive(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return True if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False return self.hero.is_alive raise exceptions.UnknownRequirementError(requirement=requirement) ################################ # satisfy requirements callbacks ################################ def satisfy_located_in(self, requirement): from . import logic object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): person_uid = object_fact.uid person = persons_storage.persons[object_fact.externals['id']] new_place_uid = uids.place(person.place.id) if new_place_uid not in self.knowledge_base: self.knowledge_base += logic.fact_place(person.place) # переписываем все ограничения в базе for fact in self.knowledge_base.filter(facts.State): for state_requirement in fact.require: if isinstance(state_requirement, requirements.LocatedIn ) and state_requirement.object == person_uid: state_requirement.place = new_place_uid return if not isinstance( object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) self._move_hero_to(destination=places_storage.places[ self.knowledge_base[requirement.place].externals['id']]) def satisfy_located_near(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance( object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) if requirement.place is None: self._move_hero_near(destination=None, terrains=requirement.terrains) else: self._move_hero_near(destination=places_storage.places.get( self.knowledge_base[requirement.place].externals['id']), terrains=requirement.terrains) def satisfy_located_on_road(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance( object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) self._move_hero_on_road( place_from=places_storage.places[self.knowledge_base[ requirement.place_from].externals['id']], place_to=places_storage.places[self.knowledge_base[ requirement.place_to].externals['id']], percents=requirement.percents) def satisfy_has_money(self, requirement): raise exceptions.UnknownRequirementError(requirement=requirement) def satisfy_is_alive(self, requirement): raise exceptions.UnknownRequirementError(requirement=requirement) ################################ # ui info ################################ def ui_info(self): return { 'line': [info.ui_info(self.hero) for info in self.quests_stack] } @classmethod def no_quests_ui_info(cls, in_place): if in_place: return {'line': [NO_QUEST_INFO__IN_PLACE.ui_info(None)]} else: return {'line': [NO_QUEST_INFO__OUT_PLACE.ui_info(None)]} @classmethod def next_spending_ui_info(cls, spending): NEXT_SPENDING_INFO = QuestInfo(type='next-spending', uid='next-spending', name='Накопить золото', action='копит', choice=None, choice_alternatives=(), experience=0, power=0, experience_bonus=0, power_bonus=0, actors={'goal': (spending, 'цель')}, used_markers=set()) return {'line': [NEXT_SPENDING_INFO.ui_info(None)]}
class MachineTests(unittest.TestCase): def setUp(self): self.kb = KnowledgeBase() self.hero = facts.Hero(uid='hero') self.start = facts.Start(uid='start', type='test', nesting=0) self.state_1 = facts.State(uid='state_1') self.state_2 = facts.State(uid='state_2') self.finish_1 = facts.Finish(start='start', uid='finish_1', results={}, nesting=0) self.kb += [ self.start, self.state_1, self.state_2, self.finish_1, self.hero] self.machine = Machine(knowledge_base=self.kb, interpreter=FakeInterpreter()) def test_get_pointer(self): self.assertEqual(list(self.kb.filter(facts.Pointer)), []) pointer = self.machine.pointer self.assertEqual(list(self.kb.filter(facts.Pointer)), [pointer]) def test_get_available_jumps__no_jumps(self): self.assertEqual(self.machine.get_available_jumps(self.start), []) def test_get_available_jumps__all_jumps(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.assertEqual(set(jump.uid for jump in self.machine.get_available_jumps(self.start)), set([jump_1.uid, jump_2.uid])) def test_get_available_jumps__for_choice_state(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) self.kb += (choice, option_1, option_2, path) for i in range(100): self.assertEqual(self.machine.get_available_jumps(choice), [option_2]) def test_get_available_jumps__for_question_state(self): condition = requirements.LocatedIn(object='object', place='place') question = facts.Question(uid='choice', condition=[condition]) answer_1 = facts.Answer(state_from=question.uid, state_to=self.state_1.uid, condition=True) answer_2 = facts.Answer(state_from=question.uid, state_to=self.state_2.uid, condition=False) self.kb += (question, answer_1, answer_2) with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=False)): for i in range(100): self.assertEqual(self.machine.get_available_jumps(question), [answer_2]) with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=True)): for i in range(100): self.assertEqual(self.machine.get_available_jumps(question), [answer_1]) def test_get_next_jump__no_jumps(self): self.assertRaises(exceptions.NoJumpsAvailableError, self.machine.get_next_jump, self.start) def test_get_next_jump__all_jumps(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] jumps = set() for i in range(100): jumps.add(self.machine.get_next_jump(self.start, single=False).uid) self.assertEqual(jumps, set([jump_1.uid, jump_2.uid])) def test_get_next_jump__require_one_jump__exception(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.assertRaises(exceptions.MoreThenOneJumpsAvailableError, self.machine.get_next_jump, self.start, single=True) def test_get_next_jump__require_one_jump(self): jump = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) self.kb += jump self.assertEqual(self.machine.get_next_jump(self.start, single=True).uid, jump.uid) def test_can_do_step__no_pointer(self): self.assertEqual(list(self.kb.filter(facts.Pointer)), []) self.assertTrue(self.machine.can_do_step()) @mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=False)) def test_can_do_step__requirement_failed(self): state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')]) jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid) self.kb += [ state_3, jump_3] pointer = self.machine.pointer self.kb -= pointer self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid) self.assertFalse(self.machine.can_do_step()) def test_can_do_step__success(self): state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')]) jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid) self.kb += [ state_3, jump_3] pointer = self.machine.pointer self.kb -= pointer self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid) with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=True)): self.assertTrue(self.machine.can_do_step()) def test_do_step__no_pointer(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.assertEqual(list(self.kb.filter(facts.Pointer)), []) self.machine.step() self.assertEqual(len(list(self.kb.filter(facts.Pointer))), 1) self.assertEqual(calls_manager.mock_calls, [mock.call.on_state__before_actions(state=self.start), mock.call.on_state__after_actions(state=self.start)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.start.uid) self.assertEqual(pointer.jump, None) def test_do_step__finish(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid) self.kb += jump_1 self.machine.step() self.machine.step() calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.step() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_end__before_actions(jump=jump_1), mock.call.on_jump_end__after_actions(jump=jump_1), mock.call.on_state__before_actions(state=self.finish_1), mock.call.on_state__after_actions(state=self.finish_1)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.finish_1.uid) self.assertEqual(pointer.jump, None) def test_do_step__step_after_finish(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid) self.kb += jump_1 self.machine.step() self.machine.step() self.machine.step() self.assertRaises(exceptions.NoJumpsFromLastStateError, self.machine.step) def test_do_step__next_jump(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.machine.step() calls_manager = mock.MagicMock() pointer = self.machine.pointer self.assertEqual(pointer.state, self.start.uid) self.assertEqual(pointer.jump, None) with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.step() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_1), mock.call.on_jump_start__after_actions(jump=jump_1)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.start.uid) self.assertEqual(pointer.jump, jump_1.uid) def test_do_step__next_state(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.machine.step() self.machine.step() self.machine.step() pointer = self.machine.pointer self.assertEqual(pointer.state, self.state_1.uid) self.assertEqual(pointer.jump, None) calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.step() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_2), mock.call.on_jump_start__after_actions(jump=jump_2)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.state_1.uid) self.assertEqual(pointer.jump, jump_2.uid) def test_sync_pointer(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) pointer = facts.Pointer(state=choice.uid, jump=option_1.uid) self.kb += (choice, option_1, option_2, path, pointer) calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.sync_pointer() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=option_2), mock.call.on_jump_start__after_actions(jump=option_2)]) self.assertEqual(self.machine.pointer.jump, option_2.uid) self.assertEqual(self.machine.pointer.state, choice.uid) def test_get_start_state(self): start_2 = facts.Start(uid='s2', type='test', nesting=0) start_3 = facts.Start(uid='start_3', type='test', nesting=0) self.kb += [ start_2, start_3, facts.Jump(state_from=self.start.uid, state_to=start_2.uid), facts.Jump(state_from=start_3.uid, state_to=self.start.uid) ] self.assertEqual(self.machine.get_start_state().uid, start_3.uid) def test_get_nearest_choice__no_choice(self): self.kb += facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) def test_get_nearest_choice__choice(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) self.kb += (choice, option_1, option_2, path, facts.Jump(state_from=self.start.uid, state_to=choice.uid) ) choice_, options_, path_ = self.machine.get_nearest_choice() self.assertEqual(choice_.uid, choice.uid) self.assertEqual(set(o.uid for o in options_), set([option_1.uid, option_2.uid])) self.assertEqual([p.uid for p in path_], [path.uid]) def test_get_nearest_choice__2_choices(self): choice_1 = facts.Choice(uid='choice_1') choice_2 = facts.Choice(uid='choice_2') option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=()) option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=()) path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True) option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=()) option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=()) path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True) self.kb += (choice_1, option_1_1, option_1_2, path_1, choice_2, option_2_1, option_2_2, path_2, facts.Jump(state_from=self.start.uid, state_to=choice_1.uid), ) choice_, options_, path_ = self.machine.get_nearest_choice() self.assertEqual(choice_.uid, choice_1.uid) self.assertEqual(set(o.uid for o in options_), set([option_1_1.uid, option_1_2.uid])) self.assertEqual([p.uid for p in path_], [path_1.uid]) def test_get_nearest_choice__2_choices__second_choice(self): choice_1 = facts.Choice(uid='choice_1') choice_2 = facts.Choice(uid='choice_2') option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=()) option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=()) path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True) option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=()) option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=()) path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True) self.kb += (choice_1, option_1_1, option_1_2, path_1, choice_2, option_2_1, option_2_2, path_2, facts.Jump(state_from=self.start.uid, state_to=choice_1.uid), facts.Pointer(state=choice_2.uid) ) choice_, options_, path_ = self.machine.get_nearest_choice() self.assertEqual(choice_.uid, choice_2.uid) self.assertEqual(set(o.uid for o in options_), set([option_2_1.uid, option_2_2.uid])) self.assertEqual([p.uid for p in path_], [path_2.uid]) def test_get_nearest_choice__choice_after_finish(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) self.kb += (choice, option_1, option_2, path, facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid), facts.Jump(state_from=self.finish_1.uid, state_to=choice.uid)) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) def test_get_nearest_choice__choice_after_question(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) question = facts.Question(uid='question', condition=()) self.kb += (choice, option_1, option_2, path, question, facts.Jump(state_from=self.start.uid, state_to=question.uid), facts.Jump(state_from=question.uid, state_to=choice.uid)) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) def test_get_nearest_choice__choice_after_start(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) start_2 = facts.Start(uid='start_2', type='test', nesting=0) self.kb += (choice, option_1, option_2, path, start_2, facts.Jump(state_from=self.start.uid, state_to=start_2.uid), facts.Jump(state_from=start_2.uid, state_to=choice.uid)) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) @mock.patch('questgen.machine.Machine.can_do_step', lambda self: True) def test_do_step__step_done(self): with mock.patch('questgen.machine.Machine.step') as step: self.assertTrue(self.machine.do_step()) self.assertEqual(step.call_args_list, [mock.call()]) @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False) @mock.patch('questgen.machine.Machine.is_processed', True) def test_do_step__quest_processed(self): with mock.patch('questgen.machine.Machine.step') as step: self.assertFalse(self.machine.do_step()) self.assertEqual(step.call_args_list, []) @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False) @mock.patch('questgen.machine.Machine.is_processed', False) @mock.patch('questgen.machine.Machine.next_state', 'next-state') def test_do_step__satisfy_requirements(self): with mock.patch('questgen.machine.Machine.step') as step: with mock.patch('questgen.machine.Machine.satisfy_requirements') as satisfy_requirements: self.assertTrue(self.machine.do_step()) self.assertEqual(step.call_args_list, []) self.assertEqual(satisfy_requirements.call_args_list, [mock.call('next-state')])
class QuestPrototype(object): def __init__(self, hero, knowledge_base, quests_stack=None, created_at=None, states_to_percents=None): self.hero = hero self.quests_stack = [] if quests_stack is None else quests_stack self.knowledge_base = knowledge_base self.machine = Machine(knowledge_base=knowledge_base, interpreter=self) self.created_at = datetime.datetime.now( ) if created_at is None else created_at self.states_to_percents = states_to_percents if states_to_percents is not None else {} @property def current_info(self): return self.quests_stack[-1] def serialize(self): return { 'quests_stack': [info.serialize() for info in self.quests_stack], 'knowledge_base': self.knowledge_base.serialize(short=True), 'created_at': time.mktime(self.created_at.timetuple()), 'states_to_percents': self.states_to_percents, } @classmethod def deserialize(cls, hero, data): return cls( knowledge_base=KnowledgeBase.deserialize(data['knowledge_base'], fact_classes=facts.FACTS), quests_stack=[ QuestInfo.deserialize(info_data) for info_data in data['quests_stack'] ], created_at=datetime.datetime.fromtimestamp(data['created_at']), states_to_percents=data['states_to_percents'], hero=hero) @property def percents(self): return self.states_to_percents.get(self.machine.pointer.state, 0.0) @property def is_processed(self): return self.machine.is_processed def get_nearest_choice(self): return self.machine.get_nearest_choice() def make_choice(self, option_uid): choice, options, defaults = self.get_nearest_choice() if self.knowledge_base[option_uid].state_from != choice.uid: return False if not any(option.uid == option_uid for option in options): return False if not defaults[0].default: return False if not transformators.change_choice(knowledge_base=self.knowledge_base, new_option_uid=option_uid, default=False): return False self.machine.sync_pointer() if self.quests_stack: self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice()) self.hero.actions.request_replane() return True ########################################### # Object operations ########################################### def process(self): self.hero.quests.mark_updated() step_result = self.machine.do_step() if self.quests_stack: self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice()) if step_result: return self.percents return 1 def _move_hero_to(self, destination, break_at=None): from the_tale.game.actions.prototypes import ActionMoveToPrototype if self.hero.position.is_walking: self._move_hero_near(destination=None, back=True) return ActionMoveToPrototype.create(hero=self.hero, destination=destination, break_at=break_at) def _move_hero_near(self, destination, terrains=None, back=False): from the_tale.game.actions.prototypes import ActionMoveNearPlacePrototype if destination is None: destination = self.hero.position.get_nearest_dominant_place() ActionMoveNearPlacePrototype.create(hero=self.hero, place=destination, back=back, terrains=terrains) def _move_hero_on_road(self, place_from, place_to, percents): if self.hero.position.is_walking: self._move_hero_near(destination=None, back=True) return path_to_position_length = self.hero.position.get_minumum_distance_to( place_from) path_from_position_length = self.hero.position.get_minumum_distance_to( place_to) full_path_length = path_to_position_length + path_from_position_length if path_to_position_length < E: current_percents = 0.0 elif path_from_position_length < E: current_percents = 1.0 else: current_percents = path_to_position_length / full_path_length if percents <= current_percents: return path_to_pass = (percents - current_percents) * full_path_length self._move_hero_to(destination=place_to, break_at=path_to_pass / path_from_position_length) def _give_power(self, hero, place, power): power = power * self.current_info.total_power if power > 0: hero.places_history.add_place(place.id) return power def _give_person_power(self, hero, person, power): power = self._give_power(hero, person.place, power) power, positive_bonus, negative_bonus = hero.modify_politics_power( power, person=person) person_uid = uids.person(person.id) has_profession_marker = [ marker for marker in self.knowledge_base.filter(facts.ProfessionMarker) if marker.person == person_uid ] if has_profession_marker: power /= len(PERSON_TYPE.records) positive_bonus /= len(PERSON_TYPE.records) negative_bonus /= len(PERSON_TYPE.records) if not hero.can_change_person_power(person): return 0 person.cmd_change_power(power, positive_bonus, negative_bonus) return power def _give_place_power(self, hero, place, power): power = self._give_power(hero, place, power) power, positive_bonus, negative_bonus = hero.modify_politics_power( power, place=place) if not hero.can_change_place_power(place): return 0 place.cmd_change_power(power, positive_bonus, negative_bonus) return power def _fight(self, action): from the_tale.game.actions.prototypes import ActionBattlePvE1x1Prototype if action.mob is not None: mob = mobs_storage[self.knowledge_base[ action.mob].externals['id']].create_mob(self.hero, is_boss=True) else: mob = mobs_storage.get_random_mob(self.hero, mercenary=action.mercenary, is_boss=True) if mob is None: mobs_storage.get_random_mob(self.hero, is_boss=True) ActionBattlePvE1x1Prototype.create(hero=self.hero, mob=mob) def give_social_power(self, quest_results): results = {} for person_uid, result in quest_results.iteritems(): person_id = self.knowledge_base[person_uid].externals['id'] if person_id not in persons_storage.persons_storage: continue results[persons_storage.persons_storage[person_id]] = result VALUABLE_RESULTS = (QUEST_RESULTS.SUCCESSED, QUEST_RESULTS.FAILED) for (person_1, quest_1_result), (person_2, quest_2_result) in itertools.combinations( results.iteritems(), 2): if quest_1_result not in VALUABLE_RESULTS or quest_2_result not in VALUABLE_RESULTS: continue connection_type = persons_storage.social_connections.get_connection_type( person_1, person_2) if connection_type is None: continue if (not ((connection_type.is_PARTNER and quest_1_result == quest_2_result) or (connection_type.is_CONCURRENT and quest_1_result != quest_2_result))): continue self._give_person_power( hero=self.hero, person=person_1, power=1 if quest_1_result == QUEST_RESULTS.SUCCESSED else -1) self._give_person_power( hero=self.hero, person=person_2, power=1 if quest_2_result == QUEST_RESULTS.SUCCESSED else -1) def _finish_quest(self, finish, hero): experience = self.modify_experience(self.current_info.experience) experience_bonus = self.modify_experience( self.current_info.experience_bonus) hero.add_experience(experience) hero.add_experience(experience_bonus, without_modifications=True) hero.statistics.change_quests_done(1) if hero.companion: hero.companion.add_experience(c.COMPANIONS_COHERENCE_EXP_PER_QUEST) for person_uid, result in finish.results.iteritems(): if result == QUEST_RESULTS.FAILED: hero.quests.add_interfered_person( self.knowledge_base[person_uid].externals['id']) for marker, default in self.current_info.used_markers.iteritems(): for change_source in HABIT_CHANGE_SOURCE.records: if change_source.quest_marker == marker and change_source.quest_default == default: self.hero.update_habits(change_source) self.give_social_power(finish.results) self.quests_stack.pop() def _give_reward(self, hero, reward_type, scale): quest_info = self.current_info scale = quest_info.get_real_reward_scale(hero, scale) if hero.can_get_artifact_for_quest(): level_delta = int(math.ceil(abs(scale))) if scale < 0: level_delta = -level_delta artifact, unequipped, sell_price = self.hero.receive_artifact( equip=False, better=False, prefered_slot=False, prefered_item=False, archetype=False, level_delta=level_delta) if artifact is not None: quest_info.process_message( knowledge_base=self.knowledge_base, hero=self.hero, message='%s_artifact' % reward_type, ext_substitution={'artifact': artifact}) return money = int(max(1, f.sell_artifact_price(hero.level) * scale)) hero.change_money(MONEY_SOURCE.EARNED_FROM_QUESTS, money) quest_info.process_message(knowledge_base=self.knowledge_base, hero=self.hero, message='%s_money' % reward_type, ext_substitution={'coins': money}) def _donothing(self, donothing_type): from the_tale.game.actions.prototypes import ActionDoNothingPrototype donothing = relations.DONOTHING_TYPE.index_value[donothing_type] writer = writers.get_writer(hero=self.hero, type=self.current_info.type, message=donothing_type, substitution={}) ActionDoNothingPrototype.create( hero=self.hero, duration=donothing.duration, messages_prefix=writer.journal_id(), messages_probability=donothing.messages_probability) @classmethod def _get_upgrdade_choice(cls, hero): choices = [relations.UPGRADE_EQUIPMENT_VARIANTS.BUY] if hero.preferences.equipment_slot and hero.equipment.get( hero.preferences.equipment_slot) is not None: choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.SHARP) if hero.equipment.get( hero.preferences.equipment_slot).can_be_broken(): choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.REPAIR) return random.choice(choices) @classmethod def upgrade_equipment_cost(cls, hero_info): return int( f.normal_action_price(hero_info.level) * ITEMS_OF_EXPENDITURE.get_quest_upgrade_equipment_fraction()) @classmethod def _upgrade_equipment(cls, process_message, hero, knowledge_base, cost): if cost is not None: cost = min(cost, hero.money) upgrade_choice = cls._get_upgrdade_choice(hero) if upgrade_choice.is_BUY: artifact, unequipped, sell_price = hero.receive_artifact( equip=True, better=True, prefered_slot=True, prefered_item=True, archetype=True) if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_ARTIFACTS, -cost) if artifact is None: process_message(knowledge_base, hero, message='upgrade__fail', ext_substitution={'coins': cost}) elif unequipped: process_message(knowledge_base, hero, message='upgrade__buy_and_change', ext_substitution={ 'coins': cost, 'artifact': artifact, 'unequipped': unequipped, 'sell_price': sell_price }) else: process_message(knowledge_base, hero, message='upgrade__buy', ext_substitution={ 'coins': cost, 'artifact': artifact }) else: if artifact is None: process_message(knowledge_base, hero, message='upgrade_free__fail', ext_substitution={}) elif unequipped: process_message(knowledge_base, hero, message='upgrade_free__buy_and_change', ext_substitution={ 'artifact': artifact, 'unequipped': unequipped, 'sell_price': sell_price }) else: process_message(knowledge_base, hero, message='upgrade_free__buy', ext_substitution={'artifact': artifact}) elif upgrade_choice.is_SHARP: artifact = hero.sharp_artifact() if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_SHARPENING, -cost) process_message(knowledge_base, hero, message='upgrade__sharp', ext_substitution={ 'coins': cost, 'artifact': artifact }) else: process_message(knowledge_base, hero, message='upgrade_free__sharp', ext_substitution={'artifact': artifact}) elif upgrade_choice.is_REPAIR: artifact = hero.repair_artifact() if cost is not None: hero.change_money(MONEY_SOURCE.SPEND_FOR_REPAIRING, -cost) process_message(knowledge_base, hero, message='upgrade__repair', ext_substitution={ 'coins': cost, 'artifact': artifact }) else: process_message(knowledge_base, hero, message='upgrade_free__repair', ext_substitution={'artifact': artifact}) else: raise exceptions.UnknownUpgadeEquipmentTypeError( type=upgrade_choice) def _start_quest(self, start, hero): hero.quests.update_history(start.type, TimePrototype.get_current_turn_number()) self.quests_stack.append( QuestInfo.construct(type=start.type, uid=start.uid, knowledge_base=self.machine.knowledge_base, hero=hero)) def modify_experience(self, experience): quest_uid = self.current_info.uid experience_modifiers = {} for participant in self.knowledge_base.filter(facts.QuestParticipant): if quest_uid != participant.start: continue fact = self.knowledge_base[participant.participant] if isinstance(fact, facts.Person): place = persons_storage.persons_storage.get( fact.externals['id']).place elif isinstance(fact, facts.Place): place = places_storage.get(fact.externals['id']) experience_modifiers[place.id] = place.get_experience_modifier() experience += experience * sum(experience_modifiers.values()) return experience ################################ # general callbacks ################################ def on_state__before_actions(self, state): if isinstance(state, facts.Start): self._start_quest(state, hero=self.hero) def on_state__after_actions(self, state): if isinstance(state, facts.Finish): self._finish_quest(state, hero=self.hero) def on_jump_start__before_actions(self, jump): pass def on_jump_start__after_actions(self, jump): pass def on_jump_end__before_actions(self, jump): pass def on_jump_end__after_actions(self, jump): if not isinstance(jump, facts.Option): return path = (path for path in self.knowledge_base.filter(facts.ChoicePath) if path.option == jump.uid).next() used_markers = self.current_info.used_markers for marker in jump.markers: for markers_group in questgen_relations.OPTION_MARKERS_GROUPS: if marker not in markers_group: continue for removed_marker in markers_group: if removed_marker in used_markers: del used_markers[removed_marker] used_markers[marker] = path.default ################################ # do action callbacks ################################ def do_message(self, action): self.current_info.process_message(self.knowledge_base, self.hero, action.type) def do_give_power(self, action): # every quest branch give equal power to its participants power = 0 if action.power > 0: power = 1 if action.power < 0: power = -1 recipient = self.knowledge_base[action.object] if isinstance(recipient, facts.Person): self._give_person_power( self.hero, persons_storage.persons_storage[recipient.externals['id']], power) elif isinstance(recipient, facts.Place): self._give_place_power(self.hero, places_storage[recipient.externals['id']], power) else: raise exceptions.UnknownPowerRecipientError(recipient=recipient) def do_give_reward(self, action): self._give_reward(self.hero, action.type, action.scale) def do_fight(self, action): self._fight(action) def do_do_nothing(self, action): self._donothing(action.type) def do_upgrade_equipment(self, action): self._upgrade_equipment(self.current_info.process_message, self.hero, self.knowledge_base, cost=action.cost) def do_move_near(self, action): if action.place: self._move_hero_near(destination=places_storage.get( self.knowledge_base[action.place].externals['id']), terrains=action.terrains) else: self._move_hero_near(destination=None, terrains=action.terrains) ################################ # check requirements callbacks ################################ def check_located_in(self, requirement): object_fact = self.knowledge_base[requirement.object] place_fact = self.knowledge_base[requirement.place] place = places_storage[place_fact.externals['id']] if isinstance(object_fact, facts.Person): person = persons_storage.persons_storage[ object_fact.externals['id']] return person.place.id == place.id if isinstance(object_fact, facts.Hero): return bool(self.hero.id == object_fact.externals['id'] and self.hero.position.place and self.hero.position.place.id == place.id) raise exceptions.UnknownRequirementError(requirement=requirement) def check_located_near(self, requirement): object_fact = self.knowledge_base[requirement.object] place_fact = self.knowledge_base[requirement.place] place = places_storage[place_fact.externals['id']] if isinstance(object_fact, facts.Person): return False if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False if self.hero.position.place: return False hero_place = self.hero.position.get_nearest_dominant_place() return place.id == hero_place.id raise exceptions.UnknownRequirementError(requirement=requirement) def check_located_on_road(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return False if not isinstance(object_fact, facts.Hero): raise exceptions.UnknownRequirementError(requirement=requirement) if self.hero.id != object_fact.externals['id']: return False place_from = places_storage[self.knowledge_base[ requirement.place_from].externals['id']] place_to = places_storage[self.knowledge_base[ requirement.place_to].externals['id']] percents = requirement.percents path_to_position_length = self.hero.position.get_minumum_distance_to( place_from) path_from_position_length = self.hero.position.get_minumum_distance_to( place_to) full_path_length = path_to_position_length + path_from_position_length if path_to_position_length < E: current_percents = 0.0 elif path_from_position_length < E: current_percents = 1.0 else: current_percents = path_to_position_length / full_path_length return current_percents >= percents def check_has_money(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return False if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False return self.hero.money >= requirement.money raise exceptions.UnknownRequirementError(requirement=requirement) def check_is_alive(self, requirement): object_fact = self.knowledge_base[requirement.object] if isinstance(object_fact, facts.Person): return True if isinstance(object_fact, facts.Hero): if self.hero.id != object_fact.externals['id']: return False return self.hero.is_alive raise exceptions.UnknownRequirementError(requirement=requirement) ################################ # satisfy requirements callbacks ################################ def satisfy_located_in(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance( object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) self._move_hero_to(destination=places_storage[self.knowledge_base[ requirement.place].externals['id']]) def satisfy_located_near(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance( object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) if requirement.place is None: self._move_hero_near(destination=None, terrains=requirement.terrains) else: self._move_hero_near(destination=places_storage.get( self.knowledge_base[requirement.place].externals['id']), terrains=requirement.terrains) def satisfy_located_on_road(self, requirement): object_fact = self.knowledge_base[requirement.object] if not isinstance( object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']: raise exceptions.UnknownRequirementError(requirement=requirement) self._move_hero_on_road(place_from=places_storage[self.knowledge_base[ requirement.place_from].externals['id']], place_to=places_storage[self.knowledge_base[ requirement.place_to].externals['id']], percents=requirement.percents) def satisfy_has_money(self, requirement): raise exceptions.UnknownRequirementError(requirement=requirement) def satisfy_is_alive(self, requirement): raise exceptions.UnknownRequirementError(requirement=requirement) ################################ # ui info ################################ def ui_info(self): return { 'line': [info.ui_info(self.hero) for info in self.quests_stack] } @classmethod def no_quests_ui_info(cls, in_place): if in_place: return {'line': [NO_QUEST_INFO__IN_PLACE.ui_info(None)]} else: return {'line': [NO_QUEST_INFO__OUT_PLACE.ui_info(None)]} @classmethod def next_spending_ui_info(cls, spending): NEXT_SPENDING_INFO = QuestInfo(type='next-spending', uid='next-spending', name=u'Накопить золото', action=u'копит', choice=None, choice_alternatives=(), experience=0, power=0, experience_bonus=0, power_bonus=0, actors={'goal': (spending, u'цель')}, used_markers=set()) return {'line': [NEXT_SPENDING_INFO.ui_info(None)]}
class MachineTests(unittest.TestCase): def setUp(self): self.kb = KnowledgeBase() self.hero = facts.Hero(uid='hero') self.start = facts.Start(uid='start', type='test', nesting=0) self.state_1 = facts.State(uid='state_1') self.state_2 = facts.State(uid='state_2') self.finish_1 = facts.Finish(start='start', uid='finish_1', results={}, nesting=0) self.kb += [ self.start, self.state_1, self.state_2, self.finish_1, self.hero] self.machine = Machine(knowledge_base=self.kb, interpreter=FakeInterpreter()) def test_get_pointer(self): self.assertEqual(list(self.kb.filter(facts.Pointer)), []) pointer = self.machine.pointer self.assertEqual(list(self.kb.filter(facts.Pointer)), [pointer]) def test_get_available_jumps__no_jumps(self): self.assertEqual(self.machine.get_available_jumps(self.start), []) def test_get_available_jumps__all_jumps(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.assertEqual(set(jump.uid for jump in self.machine.get_available_jumps(self.start)), set([jump_1.uid, jump_2.uid])) def test_get_available_jumps__for_choice_state(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) self.kb += (choice, option_1, option_2, path) for i in xrange(100): self.assertEqual(self.machine.get_available_jumps(choice), [option_2]) def test_get_available_jumps__for_question_state(self): condition = requirements.LocatedIn(object='object', place='place') question = facts.Question(uid='choice', condition=[condition]) answer_1 = facts.Answer(state_from=question.uid, state_to=self.state_1.uid, condition=True) answer_2 = facts.Answer(state_from=question.uid, state_to=self.state_2.uid, condition=False) self.kb += (question, answer_1, answer_2) with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=False)): for i in xrange(100): self.assertEqual(self.machine.get_available_jumps(question), [answer_2]) with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=True)): for i in xrange(100): self.assertEqual(self.machine.get_available_jumps(question), [answer_1]) def test_get_next_jump__no_jumps(self): self.assertRaises(exceptions.NoJumpsAvailableError, self.machine.get_next_jump, self.start) def test_get_next_jump__all_jumps(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] jumps = set() for i in xrange(100): jumps.add(self.machine.get_next_jump(self.start, single=False).uid) self.assertEqual(jumps, set([jump_1.uid, jump_2.uid])) def test_get_next_jump__require_one_jump__exception(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.assertRaises(exceptions.MoreThenOneJumpsAvailableError, self.machine.get_next_jump, self.start, single=True) def test_get_next_jump__require_one_jump(self): jump = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) self.kb += jump self.assertEqual(self.machine.get_next_jump(self.start, single=True).uid, jump.uid) def test_can_do_step__no_pointer(self): self.assertEqual(list(self.kb.filter(facts.Pointer)), []) self.assertTrue(self.machine.can_do_step()) @mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=False)) def test_can_do_step__requirement_failed(self): state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')]) jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid) self.kb += [ state_3, jump_3] pointer = self.machine.pointer self.kb -= pointer self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid) self.assertFalse(self.machine.can_do_step()) def test_can_do_step__success(self): state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')]) jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid) self.kb += [ state_3, jump_3] pointer = self.machine.pointer self.kb -= pointer self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid) with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=True)): self.assertTrue(self.machine.can_do_step()) def test_do_step__no_pointer(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.assertEqual(list(self.kb.filter(facts.Pointer)), []) self.machine.step() self.assertEqual(len(list(self.kb.filter(facts.Pointer))), 1) self.assertEqual(calls_manager.mock_calls, [mock.call.on_state__before_actions(state=self.start), mock.call.on_state__after_actions(state=self.start)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.start.uid) self.assertEqual(pointer.jump, None) def test_do_step__finish(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid) self.kb += jump_1 self.machine.step() self.machine.step() calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.step() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_end__before_actions(jump=jump_1), mock.call.on_jump_end__after_actions(jump=jump_1), mock.call.on_state__before_actions(state=self.finish_1), mock.call.on_state__after_actions(state=self.finish_1)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.finish_1.uid) self.assertEqual(pointer.jump, None) def test_do_step__step_after_finish(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid) self.kb += jump_1 self.machine.step() self.machine.step() self.machine.step() self.assertRaises(exceptions.NoJumpsFromLastStateError, self.machine.step) def test_do_step__next_jump(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.machine.step() calls_manager = mock.MagicMock() pointer = self.machine.pointer self.assertEqual(pointer.state, self.start.uid) self.assertEqual(pointer.jump, None) with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.step() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_1), mock.call.on_jump_start__after_actions(jump=jump_1)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.start.uid) self.assertEqual(pointer.jump, jump_1.uid) def test_do_step__next_state(self): jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid) jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid) self.kb += [jump_1, jump_2] self.machine.step() self.machine.step() self.machine.step() pointer = self.machine.pointer self.assertEqual(pointer.state, self.state_1.uid) self.assertEqual(pointer.jump, None) calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.step() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_2), mock.call.on_jump_start__after_actions(jump=jump_2)]) pointer = self.machine.pointer self.assertEqual(pointer.state, self.state_1.uid) self.assertEqual(pointer.jump, jump_2.uid) def test_sync_pointer(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) pointer = facts.Pointer(state=choice.uid, jump=option_1.uid) self.kb += (choice, option_1, option_2, path, pointer) calls_manager = mock.MagicMock() with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions: with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions: with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions: calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions') calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions') calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions') calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions') calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions') calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions') self.machine.sync_pointer() self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=option_2), mock.call.on_jump_start__after_actions(jump=option_2)]) self.assertEqual(self.machine.pointer.jump, option_2.uid) self.assertEqual(self.machine.pointer.state, choice.uid) def test_get_start_state(self): start_2 = facts.Start(uid='s2', type='test', nesting=0) start_3 = facts.Start(uid='start_3', type='test', nesting=0) self.kb += [ start_2, start_3, facts.Jump(state_from=self.start.uid, state_to=start_2.uid), facts.Jump(state_from=start_3.uid, state_to=self.start.uid) ] self.assertEqual(self.machine.get_start_state().uid, start_3.uid) def test_get_nearest_choice__no_choice(self): self.kb += facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) def test_get_nearest_choice__choice(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) self.kb += (choice, option_1, option_2, path, facts.Jump(state_from=self.start.uid, state_to=choice.uid) ) choice_, options_, path_ = self.machine.get_nearest_choice() self.assertEqual(choice_.uid, choice.uid) self.assertEqual(set(o.uid for o in options_), set([option_1.uid, option_2.uid])) self.assertEqual([p.uid for p in path_], [path.uid]) def test_get_nearest_choice__2_choices(self): choice_1 = facts.Choice(uid='choice_1') choice_2 = facts.Choice(uid='choice_2') option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=()) option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=()) path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True) option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=()) option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=()) path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True) self.kb += (choice_1, option_1_1, option_1_2, path_1, choice_2, option_2_1, option_2_2, path_2, facts.Jump(state_from=self.start.uid, state_to=choice_1.uid), ) choice_, options_, path_ = self.machine.get_nearest_choice() self.assertEqual(choice_.uid, choice_1.uid) self.assertEqual(set(o.uid for o in options_), set([option_1_1.uid, option_1_2.uid])) self.assertEqual([p.uid for p in path_], [path_1.uid]) def test_get_nearest_choice__2_choices__second_choice(self): choice_1 = facts.Choice(uid='choice_1') choice_2 = facts.Choice(uid='choice_2') option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=()) option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=()) path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True) option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=()) option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=()) path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True) self.kb += (choice_1, option_1_1, option_1_2, path_1, choice_2, option_2_1, option_2_2, path_2, facts.Jump(state_from=self.start.uid, state_to=choice_1.uid), facts.Pointer(state=choice_2.uid) ) choice_, options_, path_ = self.machine.get_nearest_choice() self.assertEqual(choice_.uid, choice_2.uid) self.assertEqual(set(o.uid for o in options_), set([option_2_1.uid, option_2_2.uid])) self.assertEqual([p.uid for p in path_], [path_2.uid]) def test_get_nearest_choice__choice_after_finish(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) self.kb += (choice, option_1, option_2, path, facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid), facts.Jump(state_from=self.finish_1.uid, state_to=choice.uid)) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) def test_get_nearest_choice__choice_after_question(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) question = facts.Question(uid='question', condition=()) self.kb += (choice, option_1, option_2, path, question, facts.Jump(state_from=self.start.uid, state_to=question.uid), facts.Jump(state_from=question.uid, state_to=choice.uid)) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) def test_get_nearest_choice__choice_after_start(self): choice = facts.Choice(uid='choice') option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=()) option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=()) path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True) start_2 = facts.Start(uid='start_2', type='test', nesting=0) self.kb += (choice, option_1, option_2, path, start_2, facts.Jump(state_from=self.start.uid, state_to=start_2.uid), facts.Jump(state_from=start_2.uid, state_to=choice.uid)) self.assertEqual(self.machine.get_nearest_choice(), (None, None, None)) @mock.patch('questgen.machine.Machine.can_do_step', lambda self: True) def test_do_step__step_done(self): with mock.patch('questgen.machine.Machine.step') as step: self.assertTrue(self.machine.do_step()) self.assertEqual(step.call_args_list, [mock.call()]) @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False) @mock.patch('questgen.machine.Machine.is_processed', True) def test_do_step__quest_processed(self): with mock.patch('questgen.machine.Machine.step') as step: self.assertFalse(self.machine.do_step()) self.assertEqual(step.call_args_list, []) @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False) @mock.patch('questgen.machine.Machine.is_processed', False) @mock.patch('questgen.machine.Machine.next_state', 'next-state') def test_do_step__satisfy_requirements(self): with mock.patch('questgen.machine.Machine.step') as step: with mock.patch('questgen.machine.Machine.satisfy_requirements') as satisfy_requirements: self.assertTrue(self.machine.do_step()) self.assertEqual(step.call_args_list, []) self.assertEqual(satisfy_requirements.call_args_list, [mock.call('next-state')])