def battle_skip_turn(bot, update, user_data): battle = get_battle(user_data.get('Battle id')) if battle is None: bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return player_choosing = get_player_choosing_from_battle_via_id( battle, update.message.from_user.id) player_choosing.skill = get_skill(player_choosing.participant.game_class, update.message.text) targets = [player_choosing.participant] player_choosing.targets = targets user_data.update({'status': 'Battle waiting'}) bot.sync_send_message( chat_id=player_choosing.participant.id, text="Ждем других игроков", reply_markup=cancel_button ) #TODO Сообщение должно быть до следующего сообщение о просчете битвы, sync battle.skills_queue.append(player_choosing) if battle.is_ready(): for i in range(2): for j in battle.teams[i]: if not j.is_ai: dispatcher.user_data.get(j.participant.id).update( {"Battle_waiting_to_count": 1}) put_battle_in_battles_need_treating(battle)
def add_chosen_skill(update, user_data): battle = get_battle(user_data.get('Battle id')) if battle is None: dispatcher.bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return player_choosing = get_player_choosing_from_battle_via_id( battle, update.message.from_user.id) res = player_choosing.participant.skill_avaliable(update.message.text) text = "" if res == 1: player_choosing.skill = get_skill( player_choosing.participant.game_class, update.message.text) user_data.update({'status': 'Choosing target'}) return 1 elif res == -1: text += "Такого навыка нет" elif res == -2: text += "Этот навык недоступен" elif res == -3: text += "Этот навык еще не готов" #TODO окончание слова dispatcher.bot.send_message(chat_id=update.message.from_user.id, text=text) return -1
def set_skill_on_ally_team(bot, update, user_data): battle = get_battle(user_data.get('Battle id')) if battle is None: dispatcher.bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return if add_chosen_skill(update, user_data) == -1: return player_choosing = get_player_choosing_from_battle_via_id( battle, update.message.from_user.id) targets = [] for i in battle.teams[user_data.get('Team')]: targets.append(i.participant) player_choosing.targets = targets user_data.update({'status': 'Battle waiting'}) bot.sync_send_message( chat_id=update.message.chat_id, text="Вы выбрали действие, ждем других игроков", reply_markup=cancel_button ) # TODO Сообщение должно быть до следующего сообщение о просчете битвы, sync battle.skills_queue.append(player_choosing) if battle.is_ready(): for i in range(2): for j in battle.teams[i]: dispatcher.user_data.get(j.participant.id).update( {"Battle_waiting_to_count": 1}) put_battle_in_battles_need_treating(battle)
def biomechanic_fifth_func(targets, battle, player): #Немота for i in targets: interprocess_dict = InterprocessDictionary(i.id, "user_data", {'stunned': 1 + 1}) interprocess_queue.put(interprocess_dict) battle.stun_list.update({i.nickname: 1 + 1}) player.skill_cooldown.update({'Пятый навык': 3 + 1}) return "💫"
def hacker_fifth_func(targets, battle, player): #Масс дамаг + баф на команду for i in targets: interprocess_dict = InterprocessDictionary(i.id, "user_data", {'stunned': 1 + 1}) interprocess_queue.put(interprocess_dict) battle.stun_list.update({i.nickname: 1 + 1}) player.skill_cooldown.update({'Пятый навык': 3 + 1}) return "💫"
def gunner_fifth_func(targets, battle, player): #Удар + разоружение (считай стан) for i in targets: interprocess_dict = InterprocessDictionary(i.id, "user_data", {'stunned': 1 + 1}) interprocess_queue.put(interprocess_dict) battle.stun_list.update({i.nickname: 1 + 1}) player.skill_cooldown.update({'Пятый навык': 3 + 1}) return "💫"
def choose_friendly_target(bot, update, user_data): user_data.update({'chosen skill': update.message.text}) battle = get_battle(user_data.get('Battle id')) if battle is None: bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return if add_chosen_skill(update, user_data) == -1: return bot.send_message(chat_id=update.message.chat_id, text="Выберите цель", reply_markup=get_allies_buttons(battle, user_data.get('Team')))
def battle_stunned(bot, update, user_data): battle = get_battle(user_data.get('Battle id')) if battle is None: bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return player_choosing = get_player_choosing_from_battle_via_id( battle, update.message.from_user.id) bot.send_message(chat_id=update.message.chat_id, text="Вы оглушены и не можете этого сделать", reply_markup=get_general_battle_buttons( player_choosing.participant))
def put_in_pending_battles_from_queue(): battle = treated_battles.get() while battle is not None: if battle.is_ready(): battles_need_treating.put( battle ) # Возможно, стоит тоже put_battle_in_battles_need_treating(battle) else: print("battle_id =", battle.id) pending_battles.update({battle.id: battle}) for i in range(2): for j in range(battle.team_players_count[i]): player_choosing = battle.teams[i][j] if not player_choosing.is_ai: player = player_choosing.participant interprocess_dictionary = InterprocessDictionary( player.id, "user_data", {'Battle_waiting_to_count': 0}) interprocess_queue.put(interprocess_dictionary) battle = treated_battles.get() save_battles()
def battle_cancel_choosing(bot, update, user_data): battle = get_battle(user_data.get('Battle id')) if battle is None: bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return player_choosing = get_player_choosing_from_battle_via_id( battle, update.message.from_user.id) player_choosing.skill = None player_choosing.targets = None bot.send_message(chat_id=update.message.from_user.id, text="Вы отменили выбор", reply_markup=get_general_battle_buttons( player_choosing.participant)) user_data.update({'status': 'Battle'}) if player_choosing in battle.skills_queue: battle.skills_queue.remove(player_choosing)
def set_target(bot, update, user_data): battle = get_battle(user_data.get('Battle id')) if battle is None: bot.send_message(chat_id=update.message.chat_id, text="<b>Битва не найдена!</b>", parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( update.message.from_user.id, "battle status return", {}) interprocess_queue.put(interprocess_dictionary) return player_choosing = get_player_choosing_from_battle_via_id( battle, update.message.from_user.id) new_target_choosing = get_player_choosing_from_battle_via_nick( battle, update.message.text) if battle.taunt_list.get((user_data.get('Team') + 1) % 2) and battle.taunt_list.get((user_data.get('Team') + 1) % 2).get(new_target_choosing.participant.nickname) is None\ and new_target_choosing not in battle.teams[user_data.get('Team')]: bot.send_message(chat_id=update.message.chat_id, text="Вы можете атаковать только провокаторов!") return if new_target_choosing is None: bot.send_message(chat_id=update.message.chat_id, text="Нет игрока с ником '{0}'!".format( update.message.text)) return targets = [new_target_choosing.participant] player_choosing.targets = targets user_data.update({'status': 'Battle waiting'}) bot.sync_send_message( chat_id=update.message.chat_id, text="Вы выбрали цель, ждем других игроков", reply_markup=cancel_button ) #TODO Сообщение должно быть до следующего сообщение о просчете битвы, sync battle.skills_queue.append(player_choosing) if battle.is_ready(): for i in range(2): for j in battle.teams[i]: if not j.is_ai: dispatcher.user_data.get(j.participant.id).update( {"Battle_waiting_to_count": 1}) put_battle_in_battles_need_treating(battle)
def return_to_location_admin(bot, update, user_data): player_id = update.message.chat_id player = get_player(player_id) update_status('In Location', player, user_data) update_location(player.location, player, user_data) j = travel_jobs.get(player.id) if pending_battles.get(user_data.get("Battle id")) is not None: pending_battles.pop(user_data.get("Battle id")) """list_user_data = list(user_data) if 'saved_battle_status' in list_user_data: user_data.pop('saved_battle_status') if 'chosen skill' in list_user_data: user_data.pop('chosen skill') if 'Battle waiting update' in list_user_data: user_data.pop('Battle waiting update') if 'Battle id' in list_user_data: user_data.pop('Battle id') if 'matchmaking' in list_user_data: user_data.pop('matchmaking') if 'Team' in list_user_data: user_data.pop('Team')""" if 'saved_info_status' in user_data: user_data.pop('saved_info_status') interprocess_dict = InterprocessDictionary(player.id, "battle status return", {}) interprocess_queue.put(interprocess_dict) try: grinding_players.remove(player) user_data.pop('grind_started') user_data.pop('farming_started') except Exception: pass #bot.send_message(chat_id=player_id,text="Вы вернулись в локацию: {0}".format(locations.get(player.location).name)) if j is None: return j.job.schedule_removal()
def battle_count( ): #Тут считается битва в которой все выбрали действие, отдельный процесс, Не забыть сделать так, чтобы выполнялось в таком порядке, в котором было выбрано #Возможно стоит едитить сообщение и проставлять галки для тех, кто уже готов try: while True: battle = battles_need_treating.get() try: team_strings = ["Team 1:\n", "Team 2:\n"] result_strings = ["Team 1:\n", "Team 2:\n"] damage_dict = {} for i in battle.skills_queue: try: if i.participant.nickname in battle.dead_list: # Проверка на смэрт if not i.is_ai: message_group = get_message_group( i.participant.id) dispatcher.bot.group_send_message( message_group, chat_id=i.participant.id, text="<b>Вы мертвы</b>", parse_mode="HTML", reply_markup=ReplyKeyboardRemove()) continue if i.participant.nickname in battle.stun_list: # Проверка на стан team_strings[ i. team] += "•<b>{0}</b> Оглушен на {1} ходов".format( i.participant.nickname + game_classes_to_emoji.get( i.participant.game_class), battle.stun_list.get( i.participant.nickname) - 1) continue skill_str = i.skill.use_skill(i.targets, battle, i.participant) damage = 0 if skill_str is not None: try: damage = int(skill_str) except ValueError: damage = None if damage is not None and damage >= 0: skill_str = "(+" + skill_str + ")" else: skill_str = "(" + skill_str + ")" if damage is not None and damage != 0: for t in i.targets: record = damage_dict.get(t.nickname) if record is None: damage_dict.update({t.nickname: damage}) else: damage_dict.update( {t.nickname: record + damage}) if i.skill.priority == 0: team_strings[ i.team] += i.skill.format_string.format( i.participant.nickname + game_classes_to_emoji.get( i.participant.game_class), "", "") else: if len(i.targets) > 1: team_strings[ i.team] += i.skill.format_string.format( i.participant.nickname + game_classes_to_emoji.get( i.participant.game_class), "Команда противника", "<b>" + skill_str + "</b>") else: team_strings[ i.team] += i.skill.format_string.format( i.participant.nickname + game_classes_to_emoji.get( i.participant.game_class), i.targets[0].nickname + game_classes_to_emoji.get( i.targets[0].game_class), "<b>" + skill_str + "</b>") print(battle.mode, damage) if battle.mode == "pve" and not i.participant.is_ai: if damage < 0: # damage < 0, если наносится урон # Обработка агро for target in i.targets: if target.is_ai: print(battle.aggro_list) player_agro_dict = battle.aggro_list.get( target.nickname) #current_aggro = player_agro_dict.get(i.participant.nickname)] current_aggro = None for pib in player_agro_dict: if pib.nickname == i.participant.nickname: current_aggro = pib.aggro if not current_aggro: print( "current aggro is None!" ) current_aggro = 0 print(current_aggro, damage, i.participant.aggro_prob) pib.aggro += damage * i.participant.aggro_prob print( "aggro for {0} updated, new value = {1}" .format( i.participant.nickname, current_aggro)) except Exception: if not i.is_ai: dispatcher.bot.group_send_message( get_message_group(i.participant.id), chat_id= get_player_choosing_from_battle_via_nick( battle, i.participant.nickname).participant.id, text="<b>Ошибка при обработке скиллов</b>", parse_mode="HTML") logging.error(traceback.format_exc()) team_strings[0] += '\n' battle.skills_queue.clear() for i in range(2): for j in range(battle.team_players_count[i]): player_choosing = battle.teams[i][j] player = player_choosing.participant try: if player.nickname in battle.dead_list or player.hp <= 0: result_strings[i] += "✖️" else: if player.nickname in list( battle.stun_list ) and battle.stun_list.get( player.nickname) > 1: result_strings[i] += "💫({0})".format( battle.stun_list.get(player.nickname) - 1) if battle.taunt_list.get(i).get( player.nickname ) is not None and battle.taunt_list.get(i).get( player.nickname) > 1: result_strings[i] += "🔰({0})".format( battle.taunt_list.get(i).get( player.nickname) - 1) curr_damage = damage_dict.get(player.nickname) str_damage = "" if curr_damage is not None and curr_damage != 0: str_damage += "(" str_damage += "+" if curr_damage > 0 else "" str_damage += str(curr_damage) str_damage += ")" result_strings[ i] += "<b>{0}</b>{1} {2}{4}🌡 {3}⚡️ /info_{5}\n".format( player.nickname, game_classes_to_emoji.get( player.game_class), player.hp, player.charge, str_damage, player_choosing. number) #TODO написать красиво player_buff_list = battle.buff_list.get( player.nickname) flag = 0 if player_buff_list is not None: for t in list(player_buff_list): if not player_buff_list.get(t): continue else: if flag == 0: result_strings[i] += " Баффы:\n" flag = 1 for k in player_buff_list.get(t): result_strings[ i] += " <b>{2}{0}</b> {3} на {1} ходов\n".format( k.buff, k.turns, "+" if k.buff > 0 else "", t) else: logging.error( "player_buff_list is None in battle_processing for " + player.nickname) logging.error("battle.buff_list: " + str(battle.buff_list)) class_skills = skills.get(player.game_class) flag = 0 if class_skills is not None: for t in list(class_skills.values()): if t.name not in [ 'Атака', 'Пропуск хода' ] and player.skill_cooldown.get( t.name) > 0: player.skill_cooldown.update({ t.name: player.skill_cooldown.get(t.name) - 1 }) cooldown = player.skill_cooldown.get( t.name) if cooldown == 0: continue else: if flag == 0: result_strings[ i] += " Cooldown:\n" flag = 1 result_strings[ i] += " {0} - {1} ходов\n".format( t.name, cooldown ) #TODO разобраться с окончаниями else: logging.error( "class_skills is None in battle_processing for " + player.nickname) result_strings[i] += '\n' player_choosing.targets = None player_choosing.skill = None if not player_choosing.is_ai: reply_markup = get_general_battle_buttons( player) if player.nickname in battle.dead_list: reply_markup = None message_group = get_message_group(player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text=team_strings[0] + '\n' + team_strings[1], parse_mode="HTML", reply_markup=reply_markup) except Exception: logging.error(traceback.format_exc()) if not player_choosing.is_ai: message_group = get_message_group(player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text= "<b>Ошибка при отправлении списка использованных скиллов</b>", parse_mode="HTML") #Проставление статусов и зануление for i in range(2): for j in range(battle.team_players_count[i]): player_choosing = battle.teams[i][j] if not player_choosing.is_ai: player = player_choosing.participant try: dict = {'status': 'Battle'} if player.dead == 1: dict.update({'status': 'Battle_dead'}) interprocess_dictionary = InterprocessDictionary( player.id, "user_data", dict) interprocess_queue.put(interprocess_dictionary) reply_markup = get_general_battle_buttons( player) message_group = get_message_group(player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text=result_strings[0] + "\n" + result_strings[1], parse_mode="HTML", reply_markup=reply_markup) except Exception: logging.error(traceback.format_exc()) if not player_choosing.is_ai: message_group = get_message_group( player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text= "<b>Ошибка при отправлении результата</b", parse_mode="HTML") #Смэрт и зануление for i in range(2): for j in range(battle.team_players_count[i]): player_choosing = battle.teams[i][j] player = player_choosing.participant #Проверка на смэрт if player.hp <= 0: try: battle.dead_list.append(player.nickname) player.dead = 1 player_choosing.skill = 6 player_choosing.targets = [player] try: battle.taunt_list.get(i).pop( player.nickname) except KeyError: pass try: battle.buff_list.pop(player.nickname) except KeyError: pass if not player_choosing.is_ai: message_group = get_message_group( player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text="<b>Вы мертвы!</b>", parse_mode="HTML" ) #, reply_markup=ReplyKeyboardRemove()) interprocess_dictionary = InterprocessDictionary( player.id, "user_data", {'status': 'Battle_dead'}) interprocess_queue.put( interprocess_dictionary ) #TODO сделать смерть ии except Exception: logging.error(traceback.format_exc()) if not player_choosing.is_ai: message_group = get_message_group( player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text= "<b>Ошибка при обработке смерти</b", parse_mode="HTML") #Проверка на стан elif player.nickname in list(battle.stun_list): try: stun = battle.stun_list.get(player.nickname) if stun <= 1: battle.stun_list.pop(player.nickname) interprocess_dictionary = InterprocessDictionary( player.id, "remove stun", {}) interprocess_queue.put( interprocess_dictionary) else: player_choosing.skill = get_skill( player.game_class, "Пропуск хода") player_choosing.targets = [player] battle.stun_list.update( {player.nickname: stun - 1}) if not player_choosing.is_ai: message_group = get_message_group( player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text="Вы оглушены!", reply_markup=ReplyKeyboardRemove()) interprocess_dictionary = InterprocessDictionary( player.id, "user_data", { 'stunned': battle.stun_list.get( player.nickname) }) interprocess_queue.put( interprocess_dictionary) battle.skills_queue.append(player_choosing) except Exception: logging.error(traceback.format_exc()) if not player_choosing.is_ai: message_group = get_message_group( player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text="<b>Ошибка при обработке стана</b", parse_mode="HTML") #Зануление баффов: try: player_buff_list = battle.buff_list.get( player.nickname) if player_buff_list is not None: for t in list(player_buff_list.values()): for k in t: if k.turns <= 1: t.remove(k) else: k.turns -= 1 except Exception: logging.error(traceback.format_exc()) if not player_choosing.is_ai: message_group = get_message_group(player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text="<b>Ошибка при занулении баффов</b", parse_mode="HTML") #Зануление таунтов: team_taunt_list = battle.taunt_list.get(i) if team_taunt_list is not None: for t in list(team_taunt_list): if team_taunt_list.get(t) <= 1: team_taunt_list.pop(t) else: team_taunt_list.update( {t: team_taunt_list.get(t) - 1}) #Проверка победы res = check_win(battle) for i in range(2): for j in range(battle.team_players_count[i]): if res != -1: player_choosing = battle.teams[i][j] if not player_choosing.is_ai: player = player_choosing.participant text = "" if res == 2: text = "<b>Ничья</b>" elif player_choosing.team == res: text = "<b>Ваша команда победила! + 100exp</b>" interprocess_dictionary = InterprocessDictionary( player.id, "change_player_state", { player.id: None, "exp": 100 }) interprocess_queue.put( interprocess_dictionary) else: text = "<b>Ваша команда проиграла</b>" if not player_choosing.is_ai: message_group = get_message_group( player.id) dispatcher.bot.group_send_message( message_group, chat_id=player.id, text=text, parse_mode="HTML") interprocess_dictionary = InterprocessDictionary( player.id, "battle status return", {}) interprocess_queue.put( interprocess_dictionary) #user_data = {'status' : player.saved_battle_status, 'location': player.location} #TODO Глеб, разберись, тут ошибки: AttributeError: 'Player' object has no attribute 'saved_battle_status' #show_general_buttons(dispatcher.bot, player.id, user_data, message_group) message_group.shedule_removal() else: player_choosing = battle.teams[i][j] if not player_choosing.is_ai: player = player_choosing.participant message_group = get_message_group(player.id) message_group.shedule_removal() battle.last_count_time = time.time() if res == -1: treated_battles.put(battle) except Exception: logging.error("Одна из битв упала id - " + str(battle.id) + "С ошибкой:\n") logging.error(traceback.format_exc()) except KeyboardInterrupt: return 0
def matchmaking(): try: data = matchmaking_players.get() waiting_players = [[], [], []] battles = [] while True: if data is not None: group = data.group signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGINT]) if data.add_to_matchmaking == 0: # Отмена мачмейкинга for waiting_queue in waiting_players: for player in waiting_queue: if player == data: waiting_queue.remove(player) break for battle in battles: for player_in_battle in battle.players: if player_in_battle.player == data.player: if battle.remove_player( player_in_battle, group) == 1: battles.remove(battle) dispatcher.user_data.get(data.player.id).update({ 'status': dispatcher.user_data.get( data.player.id).get('saved_battle_status') }) user_data = dispatcher.user_data.get(data.player.id) list_user_data = list(user_data) if 'matchmaking' in list_user_data: user_data.pop('matchmaking') if 'Team' in list_user_data: user_data.pop('Team') try: data = matchmaking_players.get( timeout=datetime.timedelta( minutes=2).total_seconds()) except Empty: data = None continue # Добавляю в поиск for i in range(0, len(data.game_modes)): if data.game_modes[i]: waiting_players[i].append(data.player) battle_mode = 0 for waiting_queue in waiting_players: for player in waiting_queue: battle_found = 0 for battle in battles: if battle.is_suitable(player, battle_mode, group): search_counts = players_in_search_count.get( player.id) if search_counts is None: search_counts = 0 players_in_search_count.update( {player.id: search_counts + 1}) battle.add_player(player, group) waiting_queue.remove(player) battle_found = 1 if battle.ready_to_start(): battles.remove(battle) battle.start_battle() # Удаляю из матчмейкинга в других режимах for waiting_queue in waiting_players: for player in waiting_queue: if player == data.player: waiting_queue.remove(player) break for battle in battles: for player_in_battle in battle.players: if player_in_battle.player == data.player: if battle.remove_player( player_in_battle, group) == 1: battles.remove(battle) break if not battle_found: battle = BattleStarting(0, battle_mode) battle.add_player(player, group) waiting_queue.remove(player) battles.append(battle) search_counts = players_in_search_count.get( player.id) if search_counts is None: search_counts = 0 players_in_search_count.update( {player.id: search_counts + 1}) battle_mode += 1 for battle in battles: if datetime.datetime.now( ) - battle.last_time_player_add >= MAX_TIME_WITHOUT_PLAYER: for player in battle.players: search_counts = players_in_search_count.get( player.player.id) if search_counts == 1: new_status = InterprocessDictionary( player.player.id, "user_data", {"status": "In Location"} ) #TODO сделать вход битву не только из локации interprocess_queue.put(new_status) dispatcher.bot.send_message( chat_id=player.player.id, text= "Игроки для битвы не найдены. Попробуйте позже" ) else: search_counts -= 1 players_in_search_count.update( {player.player.id: search_counts}) battles.remove(battle) signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGINT]) try: data = matchmaking_players.get(timeout=datetime.timedelta( minutes=2).total_seconds()) except Empty: data = None except KeyboardInterrupt: return 0