diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0455c60..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cSpell.words": [ - "potionchoice" - ] -} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 516af7e..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -fuzzywuzzy==0.18.0 \ No newline at end of file diff --git a/roles.txt b/roles.txt deleted file mode 100644 index 0f7f30e..0000000 --- a/roles.txt +++ /dev/null @@ -1,10 +0,0 @@ -Wolf -Seer -WhiteWolf -Cupidon -Witch -Hunter -Corbeau -Stealer -Sorcier -Savior \ No newline at end of file diff --git a/.gitignore b/werewoolf/.gitignore similarity index 99% rename from .gitignore rename to werewoolf/.gitignore index 5290d13..773fe78 100644 --- a/.gitignore +++ b/werewoolf/.gitignore @@ -214,3 +214,5 @@ __marimo__/ # Streamlit .streamlit/secrets.toml +# custom +voicesline/playersname \ No newline at end of file diff --git a/werewoolf/finger_count.py b/werewoolf/finger_count.py new file mode 100644 index 0000000..8694571 --- /dev/null +++ b/werewoolf/finger_count.py @@ -0,0 +1,87 @@ +# code inspiration from https://www.geekering.com/categories/computer-vision/marcellacavalcanti/hand-tracking-and-finger-counting-in-python-with-mediapipe/ because i dont know how to use mediapipe + +import cv2 +import mediapipe as mp +import time + +mp_drawing = mp.solutions.drawing_utils +mp_drawing_styles = mp.solutions.drawing_styles +mp_hands = mp.solutions.hands + +def count_fingers(stable_duration=2): + previous_finger_count = -1 # Initialize to an invalid count + stable_start_time = None # To track when the count became stable + cap = cv2.VideoCapture(0) + with mp_hands.Hands( + model_complexity=0, + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as hands: + + while cap.isOpened(): + success, image = cap.read() + if not success: + print("Ignoring empty camera frame.") + continue + + image.flags.writeable = False + image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + results = hands.process(image) + + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + + fingerCount = 0 # Reset fingerCount for each frame + + if results.multi_hand_landmarks: + for hand_landmarks in results.multi_hand_landmarks: + handIndex = results.multi_hand_landmarks.index(hand_landmarks) + handLabel = results.multi_handedness[handIndex].classification[0].label + + handLandmarks = [] + for landmarks in hand_landmarks.landmark: + handLandmarks.append([landmarks.x, landmarks.y]) + + # Count fingers + if handLabel == "Left" and handLandmarks[4][0] > handLandmarks[3][0]: + fingerCount += 1 + elif handLabel == "Right" and handLandmarks[4][0] < handLandmarks[3][0]: + fingerCount += 1 + + if handLandmarks[8][1] < handLandmarks[6][1]: # Index finger + fingerCount += 1 + if handLandmarks[12][1] < handLandmarks[10][1]: # Middle finger + fingerCount += 1 + if handLandmarks[16][1] < handLandmarks[14][1]: # Ring finger + fingerCount += 1 + if handLandmarks[20][1] < handLandmarks[18][1]: # Pinky + fingerCount += 1 + + # Draw hand landmarks + mp_drawing.draw_landmarks( + image, + hand_landmarks, + mp_hands.HAND_CONNECTIONS, + mp_drawing_styles.get_default_hand_landmarks_style(), + mp_drawing_styles.get_default_hand_connections_style()) + + # Display finger count + cv2.putText(image, str(fingerCount), (50, 450), cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 0, 0), 10) + #Display image + cv2.imshow('Finger Count', image) + + # Check if the finger count is stable + if fingerCount == previous_finger_count and fingerCount != 0: + if stable_start_time is None: + stable_start_time = time.time() # Start the timer + elif time.time() - stable_start_time >= stable_duration and fingerCount != 0: + cap.release() + cv2.destroyAllWindows() + return fingerCount # Return the stable finger count + else: + stable_start_time = None # Reset the timer if the count changes + + previous_finger_count = fingerCount # Update the previous count + if cv2.waitKey(5) & 0xFF == 27: + break + + return None # Return None if the loop ends without a stable count diff --git a/logger.py b/werewoolf/logger.py similarity index 96% rename from logger.py rename to werewoolf/logger.py index 3d65862..3262df6 100644 --- a/logger.py +++ b/werewoolf/logger.py @@ -1,7 +1,7 @@ import logging log = logging.getLogger(__name__) -log.setLevel(logging.DEBUG) +log.setLevel(logging.INFO) formatter = logging.Formatter( "%(asctime)s \033[1m%(levelname)s\033[0m\033[0m %(message)s" ) diff --git a/main.py b/werewoolf/main.py similarity index 53% rename from main.py rename to werewoolf/main.py index d79119b..548c507 100644 --- a/main.py +++ b/werewoolf/main.py @@ -3,11 +3,19 @@ from typing import Dict, List, Optional from dataclasses import dataclass import sys from logger import log -from fuzzywuzzy import process +from finger_count import * +from playsound import playsound +import wave +from piper import PiperVoice +import os +from art import * +from translation import * + @dataclass class Player: name: str + alias: str role: str alive: bool = True protected: bool = False @@ -26,6 +34,8 @@ class Game: # Used potions self.used_potions: [list[str]] = [] + # Language + self.language: str = "fr" # ------------------------- # setup / I/O # ------------------------- @@ -36,7 +46,7 @@ class Game: """ players_file = "players.txt" roles_file = "roles.txt" - + words_file = "randoms_words.txt" log.info("Loading lists...") try: @@ -53,20 +63,29 @@ class Game: log.critical("Roles file not found: %s", roles_file) return None + try: + with open(words_file, "r", encoding="utf-8") as rf: + alias_list = [line.strip() for line in rf if line.strip()] + except FileNotFoundError: + log.critical("Roles file not found: %s", words_file) + return None + + log.info("Load complete!") - return {"players": player_names, "roles": roles_list} + return {"players": player_names, "roles": roles_list, "alias": alias_list} # ------------------------- # player / role assignment # ------------------------- def give_roles_to_players(self, player_names: Optional[List[str]] = None, - roles_list: Optional[List[str]] = None) -> None: + roles_list: Optional[List[str]] = None, + alias_list: Optional[List[str]] = None) -> None: """ Assign a random role to each player. Accepts optional lists (returned by load_lists). If lists are not supplied, it will attempt to read files itself. """ - + number = 0 if not player_names: log.info("Players are not initialized") return @@ -77,16 +96,59 @@ class Game: log.error("Not enough roles for players (roles: %d, players: %d)", len(roles_list), len(player_names)) return + if len(alias_list) < len(player_names): + log.error("Not enough randoms words for players (words: %d, players: %d)", + len(alias_list), len(player_names)) + return available_roles = list(roles_list) random.shuffle(available_roles) + available_alias = list(alias_list) + random.shuffle(alias_list) # clear any existing players (safe to reassign) self.players.clear() - + # clear any player name audio + if not os.path.exists("./voicesline/playersname"): + os.makedirs("./voicesline/playersname") + for filename in os.listdir("./voicesline/playersname"): + file_path = os.path.join("./voicesline/playersname", filename) + # Check if it is a file (not a subdirectory) + if os.path.isfile(file_path): + os.remove(file_path) # Remove the file + for filename in os.listdir("./voicesline/alias"): + file_path = os.path.join("./voicesline/alias", filename) + # Check if it is a file (not a subdirectory) + if os.path.isfile(file_path): + os.remove(file_path) # Remove the file + if self.language == "fr": + voice = PiperVoice.load("./voices/fr/fr_FR-tom-medium.onnx") + elif self.language == "en": + voice = PiperVoice.load("./voices/en/en_GB-alan-medium.onnx") + else: + log.info("Unknow language") for name in player_names: chosen_role = available_roles.pop() - self.players[name] = Player(name=name, role=chosen_role) + chosen_alias = available_alias.pop() + with wave.open(f"./voicesline/playersname/{name}.wav", "wb") as wav_file: + voice.synthesize_wav(name, wav_file) + with wave.open(f"./voicesline/alias/{chosen_alias}.wav", "wb") as wav_file: + voice.synthesize_wav(chosen_alias, wav_file) + number += 1 + self.players[number] = Player(name=name, role=chosen_role, alias=chosen_alias) + self.play_sound("players", name.capitalize()) + self.play_sound("prompts", "first_wakes") + if name == "Dan": + self.play_sound("prompts", "cheater") + self.show_text(chosen_role) + self.show_text(chosen_alias) + time.sleep(3) + os.system('clear') + self.play_sound("players", name.capitalize()) + self.play_sound("prompts", "sleep_again") + + + log.debug("Assigned roles:") for player in self.players.values(): @@ -95,56 +157,90 @@ class Game: # ------------------------- # utilities # ------------------------- + def clear_data(self) -> None: + for player in self.players: + self.players[player].protected = False + + + def couple(self) -> None: + os.system('clear') + self.play_sound("prompts", "lovers") + log.debug(self.lovers) + for player in self.lovers: + log.info(self.players[player].alias) + self.play_sound("alias", self.players[player].alias) + self.show_text(self.players[player].role) + time.sleep(10) + + def show_text(self, text: str) -> None: + if self.language == "fr": + if text in FR: + Art=text2art(FR.get(text)) + print(Art) + else: + Art=text2art(text) + print(Art) + if self.language == "en": + Art=text2art(text) + print(Art) + + def get_remaining_roles(self) -> List[str]: + remaining_roles = [] + for player in self.players: + if self.players[player].alive: + remaining_roles.append(self.players[player].role) + log.DEBUG(remaining_roles) + return remaining_roles + + def number_2_name(self, player_number: int): + player_name = self.players[player_number].name + return player_name + + def play_sound(self, type, soundinfo) -> None: + if type == "prompts": + f = f"./voicesline/prompts/{self.language}/{soundinfo}.wav" + elif type == "roles": + f = f"./voicesline/roles/{self.language}/{soundinfo}.wav" + elif type == "players": + f = f"./voicesline/playersname/{soundinfo}.wav" + elif type == "alias": + f = f"./voicesline/alias/{soundinfo}.wav" + else: + log.debug("An error occurred") + return + time.sleep(0.2) + playsound(f) + def select_someone(self, prompt: Optional[str] = None) -> Optional[str]: """ Prompt the user to enter a player name until a valid one is entered. Returns None on EOF/KeyboardInterrupt. """ - prompt = prompt or "Enter the name of the player: " + prompt = prompt or "show_number" + #self.play_sound("prompts", prompt) while True: - selected = input(prompt).strip() + selected = count_fingers() # exact match if selected in self.players: return selected - # fuzzy matching - match = process.extract(selected, self.players, limit=1) - log.debug(match) - if match: - fuzz_player, fuzz_score = match[0][0], match[0][1] # player name, score - log.debug(fuzz_player) - log.debug(fuzz_score) - if fuzz_score >= 60: - log.info("You meant %s!", fuzz_player.name) - log.debug("Fuzz score: %s" % fuzz_score) - return fuzz_player.name - def choose_between(self, options): #Prompt the user to choose between options until a valid one is entered - - options_available = ', '.join(map(str, options)) - #Make a prompt with a list of options, accepting either a list or a tuple. - prompt = f"Choose between {options_available}: " + numbered_options = " ".join([f"{i+1}: {option}" for i, option in enumerate(options)]) + log.info(f"Choose between {numbered_options}:") while True: - selected = input(prompt) - - if selected in options: - return selected - - # fuzzy matching - match = process.extract(selected, options, limit=1) - log.debug(match) - if match: - fuzz_option, fuzz_score = match[0][0], match[0][1] #options, score - log.debug(fuzz_option) - log.debug(fuzz_score) - if fuzz_score >= 60: - log.info("You meant %s!", fuzz_option) - log.debug("Fuzz score: %s" % fuzz_score) - return fuzz_option - - + selected = count_fingers() + try: + index = int(selected) - 1 + if 0 <= index < len(options): + chosen_option = options[index] + log.debug("You chose %s!", chosen_option) + return chosen_option + else: + log.warning("Invalid number entered.") + except ValueError: + log.warning("Invalid input. Please enter an option or number.") # ------------------------- # game actions # ------------------------- @@ -156,7 +252,7 @@ class Game: self.lovers = (player1, player2) - log.debug("Players now put in love: %s <-> %s", player1, player2) + log.debug("Players now put in love: %s <-> %s", self.players[player1], self.players[player2]) log.info("They are now bounded by love.") log.info("They wake up and reveal their role to each other.") @@ -220,21 +316,22 @@ class Game: log.debug("%s -> role: %s, alive: %s, Protected: %s", p.name, p.role, p.alive, p.protected) # lovers - log.debug("Lovers: %s" % ",".join(self.lovers)) + log.debug(f"Lovers: {self.lovers}") def role(func): """Decorator for roles""" - def wrapper(*args, **kwargs): + def wrapper(self, *args, **kwargs): # the role is the name of the function thats decorated role = func.__name__.capitalize() - log.info("%s wakes up." % role) - func(*args, **kwargs) - log.info("%s goes back to sleep." % role) - - # workaround to call status from the instance - # is it ok? no idea but i dont have self anyway - instance = args[0] - instance.status() + os.system('clear') + if role in self.get_remaining_roles(): + self.play_sound("roles", role) + self.play_sound("prompts", "wakes") + func(self, *args, **kwargs) + self.play_sound("roles", role) + self.play_sound("prompts", "sleep_again") + log.INFO("%s goes back to sleep." % role) + self.status() return wrapper @@ -245,12 +342,12 @@ class Game: @role def cupidon(self) -> None: """Interactively pick two players to link by love.""" - log.info("Choose two people to be linked to death.") - log.info("Give me the first person that shall be bounded!") + self.play_sound("prompts", "linked_death") + self.play_sound("prompts", "first_bound") p1 = self.select_someone() if p1 is None: return - log.info("What about the second one?") + self.play_sound("prompts", "second_bound") p2 = self.select_someone() if p2 is None: return @@ -262,21 +359,23 @@ class Game: @role def savior(self) -> None: """Interactively choose someone to protect.""" - log.info("Choose someone to protect.") + self.play_sound("prompts", "choose_protect") protected = self.select_someone() if protected is None: - return - + return self.players[protected].protected = True log.debug("Protected: %s", protected) @role def witch(self) -> None: """Interactively choose to kill or revive someone""" - log.info("With the Revive Potion, you can revive someone, and with the Death Potion, you can kill someone. You can only use each potion once, and you can also choose to do nothing.") - log.info("Players who might die are %s", ' '.join(map(str, self.dead_this_night))) + self.play_sound("prompts", "potions_choice") + self.play_sound("prompts", "might_die") + for player in self.dead_this_night: + self.show_text(self.players[player].name) if len(self.used_potions) >= 2: - log.info("You already used all of your potions. ") + self.show_text("Vous avez utilisé toutes vos potions. ") + time.sleep(5) else: while True: options = ("Death", "Revive", "Nothing") @@ -288,9 +387,9 @@ class Game: self.revive(player) return elif player not in self.dead_this_night and not self.players[player].alive: - log.info("You cannot bring this person back to life because they have been buried.") + self.show_text("Tu ne peux pas faire revivre cette personne.") elif self.players[player].alive: - log.info("This player is not dead.") + self.show_text("Cette personne n'est pas morte.") else: log.info("Unknown error: Invalid player choice") return @@ -299,14 +398,15 @@ class Game: if self.players[player].alive: self.used_potions.append("Death") self.kill(player) + time.sleep(1) return elif not self.players[player].alive: - log.info("This player is already dead.") + self.show_text("Ce joueur est déjà mort.") else: log.info("Unknown error: Invalid player choice") return elif potionchoice == "Nothing": - log.info("You are not doing anything tonight.") + self.show_text("Vous ne faites rien cette nuit.") return elif potionchoice in self.used_potions: log.info("You already used this potion.") @@ -315,7 +415,7 @@ class Game: return @role def werewolf(self) -> None: - log.info("You will choose someone to kill.") + self.play_sound("prompts", "werewolf_choice") player = self.select_someone() self.kill(player) @@ -325,21 +425,56 @@ class Game: while True: player = self.select_someone() if "Seer" == self.players[player].role: - log.info("You can't see your own role.") + self.show_text("You can't see your own role.") else: - log.info(f"{player} is a {self.players[player].role}") + self.show_text(self.players[player].role) + time.sleep(3) return + + @role + def hunter(self) -> None: + self.play_sound("prompts", "hunter") + player = self.select_someone() + self.kill(player) + # ------------------------- # game flow # ------------------------- - def first_day_cycle(self) -> None: - log.info("All the villagers fall asleep.") + def first_night_cycle(self) -> None: + self.play_sound("prompts", "fall_asleep_all") self.cupidon() + self.couple() self.savior() self.werewolf() self.witch() self.seer() + self.day() + def night(self) -> None: + self.dead_this_night = [] + self.play_sound("prompts", "fall_asleep_all") + self.savior() + self.werewolf() + self.witch() + self.seer() + self.day() + + def day(self) -> None: + self.play_sound("prompts", "wake_up_all") + self.play_sound("prompts", "night_deaths") + for player in self.dead_this_night: + self.play_sound("players", self.number_2_name(player)) + for player in self.dead_this_night: + if self.players[player].role == "Hunter": + self.hunter() + self.play_sound("prompts", "village_vote") + while True: + if count_fingers(stable_duration=4) == 6: + break + self.play_sound("prompts", "now_show_number") + player = self.select_someone() + self.kill(player) + self.night() @@ -354,8 +489,8 @@ if __name__ == "__main__": try: if loaded: # start the game! - game.give_roles_to_players(loaded["players"], loaded["roles"]) - game.first_day_cycle() + game.give_roles_to_players(loaded["players"], loaded["roles"], loaded["alias"]) + game.first_night_cycle() # CTRL+C except KeyboardInterrupt: diff --git a/players.txt b/werewoolf/players.txt similarity index 53% rename from players.txt rename to werewoolf/players.txt index e0f4762..f11ae93 100644 --- a/players.txt +++ b/werewoolf/players.txt @@ -1,10 +1,6 @@ -Marie -Jeanne -Paul +Anavoi Jean -Michel +Paul Christophe -Anne +Jeanne Julien -Aurélie -Yoxu \ No newline at end of file diff --git a/werewoolf/randoms_words.txt b/werewoolf/randoms_words.txt new file mode 100644 index 0000000..7ce4242 --- /dev/null +++ b/werewoolf/randoms_words.txt @@ -0,0 +1,11 @@ +Abricot +Pomme +Fraise +Melon +Orange +Framboise +Cerise +Citron +Poire +Raisin +Prune \ No newline at end of file diff --git a/werewoolf/requirements.txt b/werewoolf/requirements.txt new file mode 100644 index 0000000..c638c1a --- /dev/null +++ b/werewoolf/requirements.txt @@ -0,0 +1,7 @@ +fuzzywuzzy==0.18.0 +opencv_python +mediapipe +piper-tts +playsound +art +pygobject \ No newline at end of file diff --git a/werewoolf/roles.txt b/werewoolf/roles.txt new file mode 100644 index 0000000..fcc099d --- /dev/null +++ b/werewoolf/roles.txt @@ -0,0 +1,10 @@ +Werewolf +Seer +Witch +Grandchild +Hunter +Werewolf +Werewolf +Villager +Cupidon +Villager \ No newline at end of file diff --git a/werewoolf/translation.py b/werewoolf/translation.py new file mode 100644 index 0000000..d94f467 --- /dev/null +++ b/werewoolf/translation.py @@ -0,0 +1,10 @@ +FR = { + "Seer": "Voyante", + "Werewolf": "Loups-Garous", + "Witch": "Sorciere", + "Cupidon": "Cupidon", + "Savior": "Salvateur", + "Villager": "Villageois", + "Grandchild": "Petite-Fille", + "Hunter": "Chasseur" +} \ No newline at end of file diff --git a/werewoolf/voices/fr/fr_FR-tom-medium.onnx b/werewoolf/voices/fr/fr_FR-tom-medium.onnx new file mode 100644 index 0000000..880e3e8 Binary files /dev/null and b/werewoolf/voices/fr/fr_FR-tom-medium.onnx differ diff --git a/werewoolf/voices/fr/fr_FR-tom-medium.onnx.json b/werewoolf/voices/fr/fr_FR-tom-medium.onnx.json new file mode 100644 index 0000000..526f2bd --- /dev/null +++ b/werewoolf/voices/fr/fr_FR-tom-medium.onnx.json @@ -0,0 +1,502 @@ +{ + "dataset": "tom", + "audio": { + "sample_rate": 44100, + "quality": "medium" + }, + "espeak": { + "voice": "fr" + }, + "language": { + "code": "fr_FR", + "family": "fr", + "region": "FR", + "name_native": "Français", + "name_english": "French", + "country_english": "France" + }, + "inference": { + "noise_scale": 0.667, + "length_scale": 1, + "noise_w": 0.8 + }, + "phoneme_type": "espeak", + "phoneme_map": {}, + "phoneme_id_map": { + " ": [ + 3 + ], + "!": [ + 4 + ], + "\"": [ + 150 + ], + "#": [ + 149 + ], + "$": [ + 2 + ], + "'": [ + 5 + ], + "(": [ + 6 + ], + ")": [ + 7 + ], + ",": [ + 8 + ], + "-": [ + 9 + ], + ".": [ + 10 + ], + "0": [ + 130 + ], + "1": [ + 131 + ], + "2": [ + 132 + ], + "3": [ + 133 + ], + "4": [ + 134 + ], + "5": [ + 135 + ], + "6": [ + 136 + ], + "7": [ + 137 + ], + "8": [ + 138 + ], + "9": [ + 139 + ], + ":": [ + 11 + ], + ";": [ + 12 + ], + "?": [ + 13 + ], + "X": [ + 156 + ], + "^": [ + 1 + ], + "_": [ + 0 + ], + "a": [ + 14 + ], + "b": [ + 15 + ], + "c": [ + 16 + ], + "d": [ + 17 + ], + "e": [ + 18 + ], + "f": [ + 19 + ], + "g": [ + 154 + ], + "h": [ + 20 + ], + "i": [ + 21 + ], + "j": [ + 22 + ], + "k": [ + 23 + ], + "l": [ + 24 + ], + "m": [ + 25 + ], + "n": [ + 26 + ], + "o": [ + 27 + ], + "p": [ + 28 + ], + "q": [ + 29 + ], + "r": [ + 30 + ], + "s": [ + 31 + ], + "t": [ + 32 + ], + "u": [ + 33 + ], + "v": [ + 34 + ], + "w": [ + 35 + ], + "x": [ + 36 + ], + "y": [ + 37 + ], + "z": [ + 38 + ], + "æ": [ + 39 + ], + "ç": [ + 40 + ], + "ð": [ + 41 + ], + "ø": [ + 42 + ], + "ħ": [ + 43 + ], + "ŋ": [ + 44 + ], + "œ": [ + 45 + ], + "ǀ": [ + 46 + ], + "ǁ": [ + 47 + ], + "ǂ": [ + 48 + ], + "ǃ": [ + 49 + ], + "ɐ": [ + 50 + ], + "ɑ": [ + 51 + ], + "ɒ": [ + 52 + ], + "ɓ": [ + 53 + ], + "ɔ": [ + 54 + ], + "ɕ": [ + 55 + ], + "ɖ": [ + 56 + ], + "ɗ": [ + 57 + ], + "ɘ": [ + 58 + ], + "ə": [ + 59 + ], + "ɚ": [ + 60 + ], + "ɛ": [ + 61 + ], + "ɜ": [ + 62 + ], + "ɞ": [ + 63 + ], + "ɟ": [ + 64 + ], + "ɠ": [ + 65 + ], + "ɡ": [ + 66 + ], + "ɢ": [ + 67 + ], + "ɣ": [ + 68 + ], + "ɤ": [ + 69 + ], + "ɥ": [ + 70 + ], + "ɦ": [ + 71 + ], + "ɧ": [ + 72 + ], + "ɨ": [ + 73 + ], + "ɪ": [ + 74 + ], + "ɫ": [ + 75 + ], + "ɬ": [ + 76 + ], + "ɭ": [ + 77 + ], + "ɮ": [ + 78 + ], + "ɯ": [ + 79 + ], + "ɰ": [ + 80 + ], + "ɱ": [ + 81 + ], + "ɲ": [ + 82 + ], + "ɳ": [ + 83 + ], + "ɴ": [ + 84 + ], + "ɵ": [ + 85 + ], + "ɶ": [ + 86 + ], + "ɸ": [ + 87 + ], + "ɹ": [ + 88 + ], + "ɺ": [ + 89 + ], + "ɻ": [ + 90 + ], + "ɽ": [ + 91 + ], + "ɾ": [ + 92 + ], + "ʀ": [ + 93 + ], + "ʁ": [ + 94 + ], + "ʂ": [ + 95 + ], + "ʃ": [ + 96 + ], + "ʄ": [ + 97 + ], + "ʈ": [ + 98 + ], + "ʉ": [ + 99 + ], + "ʊ": [ + 100 + ], + "ʋ": [ + 101 + ], + "ʌ": [ + 102 + ], + "ʍ": [ + 103 + ], + "ʎ": [ + 104 + ], + "ʏ": [ + 105 + ], + "ʐ": [ + 106 + ], + "ʑ": [ + 107 + ], + "ʒ": [ + 108 + ], + "ʔ": [ + 109 + ], + "ʕ": [ + 110 + ], + "ʘ": [ + 111 + ], + "ʙ": [ + 112 + ], + "ʛ": [ + 113 + ], + "ʜ": [ + 114 + ], + "ʝ": [ + 115 + ], + "ʟ": [ + 116 + ], + "ʡ": [ + 117 + ], + "ʢ": [ + 118 + ], + "ʦ": [ + 155 + ], + "ʰ": [ + 145 + ], + "ʲ": [ + 119 + ], + "ˈ": [ + 120 + ], + "ˌ": [ + 121 + ], + "ː": [ + 122 + ], + "ˑ": [ + 123 + ], + "˞": [ + 124 + ], + "ˤ": [ + 146 + ], + "̃": [ + 141 + ], + "̧": [ + 140 + ], + "̩": [ + 144 + ], + "̪": [ + 142 + ], + "̯": [ + 143 + ], + "̺": [ + 152 + ], + "̻": [ + 153 + ], + "β": [ + 125 + ], + "ε": [ + 147 + ], + "θ": [ + 126 + ], + "χ": [ + 127 + ], + "ᵻ": [ + 128 + ], + "↑": [ + 151 + ], + "↓": [ + 148 + ], + "ⱱ": [ + 129 + ] + }, + "num_symbols": 256, + "num_speakers": 1, + "speaker_id_map": {}, + "piper_version": "1.0.0" +} diff --git a/werewoolf/voicesline/prompts/en/bound.wav b/werewoolf/voicesline/prompts/en/bound.wav new file mode 100644 index 0000000..a8f3134 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/bound.wav differ diff --git a/werewoolf/voicesline/prompts/en/choose_protect.wav b/werewoolf/voicesline/prompts/en/choose_protect.wav new file mode 100644 index 0000000..fc97449 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/choose_protect.wav differ diff --git a/werewoolf/voicesline/prompts/en/fall_asleep_all.wav b/werewoolf/voicesline/prompts/en/fall_asleep_all.wav new file mode 100644 index 0000000..56d2905 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/fall_asleep_all.wav differ diff --git a/werewoolf/voicesline/prompts/en/first_bound.wav b/werewoolf/voicesline/prompts/en/first_bound.wav new file mode 100644 index 0000000..bc697cf Binary files /dev/null and b/werewoolf/voicesline/prompts/en/first_bound.wav differ diff --git a/werewoolf/voicesline/prompts/en/hunter.wav b/werewoolf/voicesline/prompts/en/hunter.wav new file mode 100644 index 0000000..80bb54c Binary files /dev/null and b/werewoolf/voicesline/prompts/en/hunter.wav differ diff --git a/werewoolf/voicesline/prompts/en/linked_death.wav b/werewoolf/voicesline/prompts/en/linked_death.wav new file mode 100644 index 0000000..d50a0ed Binary files /dev/null and b/werewoolf/voicesline/prompts/en/linked_death.wav differ diff --git a/werewoolf/voicesline/prompts/en/lovers.wav b/werewoolf/voicesline/prompts/en/lovers.wav new file mode 100644 index 0000000..2d1e3d1 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/lovers.wav differ diff --git a/werewoolf/voicesline/prompts/en/might_die.wav b/werewoolf/voicesline/prompts/en/might_die.wav new file mode 100644 index 0000000..a0d9c1f Binary files /dev/null and b/werewoolf/voicesline/prompts/en/might_die.wav differ diff --git a/werewoolf/voicesline/prompts/en/night_deaths.wav b/werewoolf/voicesline/prompts/en/night_deaths.wav new file mode 100644 index 0000000..57b0028 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/night_deaths.wav differ diff --git a/werewoolf/voicesline/prompts/en/now_show_number.wav b/werewoolf/voicesline/prompts/en/now_show_number.wav new file mode 100644 index 0000000..9b66c8d Binary files /dev/null and b/werewoolf/voicesline/prompts/en/now_show_number.wav differ diff --git a/werewoolf/voicesline/prompts/en/potions_choice.wav b/werewoolf/voicesline/prompts/en/potions_choice.wav new file mode 100644 index 0000000..571acf2 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/potions_choice.wav differ diff --git a/werewoolf/voicesline/prompts/en/reveal.wav b/werewoolf/voicesline/prompts/en/reveal.wav new file mode 100644 index 0000000..fcb8b25 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/reveal.wav differ diff --git a/werewoolf/voicesline/prompts/en/second_bound.wav b/werewoolf/voicesline/prompts/en/second_bound.wav new file mode 100644 index 0000000..782ed53 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/second_bound.wav differ diff --git a/werewoolf/voicesline/prompts/en/sleep.wav b/werewoolf/voicesline/prompts/en/sleep.wav new file mode 100644 index 0000000..c4d2032 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/sleep.wav differ diff --git a/werewoolf/voicesline/prompts/en/sleep_again.wav b/werewoolf/voicesline/prompts/en/sleep_again.wav new file mode 100644 index 0000000..24696af Binary files /dev/null and b/werewoolf/voicesline/prompts/en/sleep_again.wav differ diff --git a/werewoolf/voicesline/prompts/en/village_vote.wav b/werewoolf/voicesline/prompts/en/village_vote.wav new file mode 100644 index 0000000..821695f Binary files /dev/null and b/werewoolf/voicesline/prompts/en/village_vote.wav differ diff --git a/werewoolf/voicesline/prompts/en/wake_up_all.wav b/werewoolf/voicesline/prompts/en/wake_up_all.wav new file mode 100644 index 0000000..4eb3caa Binary files /dev/null and b/werewoolf/voicesline/prompts/en/wake_up_all.wav differ diff --git a/werewoolf/voicesline/prompts/en/wakes.wav b/werewoolf/voicesline/prompts/en/wakes.wav new file mode 100644 index 0000000..e8f5aa3 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/wakes.wav differ diff --git a/werewoolf/voicesline/prompts/en/werewolf_choice.wav b/werewoolf/voicesline/prompts/en/werewolf_choice.wav new file mode 100644 index 0000000..7beba37 Binary files /dev/null and b/werewoolf/voicesline/prompts/en/werewolf_choice.wav differ diff --git a/werewoolf/voicesline/prompts/fr/bound.wav b/werewoolf/voicesline/prompts/fr/bound.wav new file mode 100644 index 0000000..0957a70 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/bound.wav differ diff --git a/werewoolf/voicesline/prompts/fr/cheater.wav b/werewoolf/voicesline/prompts/fr/cheater.wav new file mode 100644 index 0000000..334dfdb Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/cheater.wav differ diff --git a/werewoolf/voicesline/prompts/fr/choose_protect.wav b/werewoolf/voicesline/prompts/fr/choose_protect.wav new file mode 100644 index 0000000..6b006fa Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/choose_protect.wav differ diff --git a/werewoolf/voicesline/prompts/fr/fall_asleep_all.wav b/werewoolf/voicesline/prompts/fr/fall_asleep_all.wav new file mode 100644 index 0000000..8d65031 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/fall_asleep_all.wav differ diff --git a/werewoolf/voicesline/prompts/fr/first_bound.wav b/werewoolf/voicesline/prompts/fr/first_bound.wav new file mode 100644 index 0000000..31b3c10 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/first_bound.wav differ diff --git a/werewoolf/voicesline/prompts/fr/first_wakes.wav b/werewoolf/voicesline/prompts/fr/first_wakes.wav new file mode 100644 index 0000000..e49ce3b Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/first_wakes.wav differ diff --git a/werewoolf/voicesline/prompts/fr/hunter.wav b/werewoolf/voicesline/prompts/fr/hunter.wav new file mode 100644 index 0000000..3188118 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/hunter.wav differ diff --git a/werewoolf/voicesline/prompts/fr/linked_death.wav b/werewoolf/voicesline/prompts/fr/linked_death.wav new file mode 100644 index 0000000..028e07f Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/linked_death.wav differ diff --git a/werewoolf/voicesline/prompts/fr/lovers.wav b/werewoolf/voicesline/prompts/fr/lovers.wav new file mode 100644 index 0000000..e5cc0f6 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/lovers.wav differ diff --git a/werewoolf/voicesline/prompts/fr/might_die.wav b/werewoolf/voicesline/prompts/fr/might_die.wav new file mode 100644 index 0000000..022b8d8 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/might_die.wav differ diff --git a/werewoolf/voicesline/prompts/fr/night_deaths.wav b/werewoolf/voicesline/prompts/fr/night_deaths.wav new file mode 100644 index 0000000..5800794 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/night_deaths.wav differ diff --git a/werewoolf/voicesline/prompts/fr/now_show_number.wav b/werewoolf/voicesline/prompts/fr/now_show_number.wav new file mode 100644 index 0000000..cd97fe1 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/now_show_number.wav differ diff --git a/werewoolf/voicesline/prompts/fr/potions_choice.wav b/werewoolf/voicesline/prompts/fr/potions_choice.wav new file mode 100644 index 0000000..aaeba83 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/potions_choice.wav differ diff --git a/werewoolf/voicesline/prompts/fr/reveal.wav b/werewoolf/voicesline/prompts/fr/reveal.wav new file mode 100644 index 0000000..a9345f7 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/reveal.wav differ diff --git a/werewoolf/voicesline/prompts/fr/second_bound.wav b/werewoolf/voicesline/prompts/fr/second_bound.wav new file mode 100644 index 0000000..781eca7 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/second_bound.wav differ diff --git a/werewoolf/voicesline/prompts/fr/sleep.wav b/werewoolf/voicesline/prompts/fr/sleep.wav new file mode 100644 index 0000000..6e31abe Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/sleep.wav differ diff --git a/werewoolf/voicesline/prompts/fr/sleep_again.wav b/werewoolf/voicesline/prompts/fr/sleep_again.wav new file mode 100644 index 0000000..b3da3ac Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/sleep_again.wav differ diff --git a/werewoolf/voicesline/prompts/fr/village_vote.wav b/werewoolf/voicesline/prompts/fr/village_vote.wav new file mode 100644 index 0000000..875fa71 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/village_vote.wav differ diff --git a/werewoolf/voicesline/prompts/fr/wake_up_all.wav b/werewoolf/voicesline/prompts/fr/wake_up_all.wav new file mode 100644 index 0000000..b3b5504 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/wake_up_all.wav differ diff --git a/werewoolf/voicesline/prompts/fr/wakes.wav b/werewoolf/voicesline/prompts/fr/wakes.wav new file mode 100644 index 0000000..92bd902 Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/wakes.wav differ diff --git a/werewoolf/voicesline/prompts/fr/werewolf_choice.wav b/werewoolf/voicesline/prompts/fr/werewolf_choice.wav new file mode 100644 index 0000000..d0d460a Binary files /dev/null and b/werewoolf/voicesline/prompts/fr/werewolf_choice.wav differ diff --git a/werewoolf/voicesline/roles/en/Cupidon.wav b/werewoolf/voicesline/roles/en/Cupidon.wav new file mode 100644 index 0000000..1c9b15c Binary files /dev/null and b/werewoolf/voicesline/roles/en/Cupidon.wav differ diff --git a/werewoolf/voicesline/roles/en/Hunter.wav b/werewoolf/voicesline/roles/en/Hunter.wav new file mode 100644 index 0000000..0bd6d49 Binary files /dev/null and b/werewoolf/voicesline/roles/en/Hunter.wav differ diff --git a/werewoolf/voicesline/roles/en/Savior.wav b/werewoolf/voicesline/roles/en/Savior.wav new file mode 100644 index 0000000..577c354 Binary files /dev/null and b/werewoolf/voicesline/roles/en/Savior.wav differ diff --git a/werewoolf/voicesline/roles/en/Seer.wav b/werewoolf/voicesline/roles/en/Seer.wav new file mode 100644 index 0000000..150f33a Binary files /dev/null and b/werewoolf/voicesline/roles/en/Seer.wav differ diff --git a/werewoolf/voicesline/roles/en/Werewolf.wav b/werewoolf/voicesline/roles/en/Werewolf.wav new file mode 100644 index 0000000..1b08b7f Binary files /dev/null and b/werewoolf/voicesline/roles/en/Werewolf.wav differ diff --git a/werewoolf/voicesline/roles/en/Witch.wav b/werewoolf/voicesline/roles/en/Witch.wav new file mode 100644 index 0000000..e980847 Binary files /dev/null and b/werewoolf/voicesline/roles/en/Witch.wav differ diff --git a/werewoolf/voicesline/roles/fr/Cupidon.wav b/werewoolf/voicesline/roles/fr/Cupidon.wav new file mode 100644 index 0000000..37dfd77 Binary files /dev/null and b/werewoolf/voicesline/roles/fr/Cupidon.wav differ diff --git a/werewoolf/voicesline/roles/fr/Hunter.wav b/werewoolf/voicesline/roles/fr/Hunter.wav new file mode 100644 index 0000000..bc7d2b0 Binary files /dev/null and b/werewoolf/voicesline/roles/fr/Hunter.wav differ diff --git a/werewoolf/voicesline/roles/fr/Savior.wav b/werewoolf/voicesline/roles/fr/Savior.wav new file mode 100644 index 0000000..5715ef4 Binary files /dev/null and b/werewoolf/voicesline/roles/fr/Savior.wav differ diff --git a/werewoolf/voicesline/roles/fr/Seer.wav b/werewoolf/voicesline/roles/fr/Seer.wav new file mode 100644 index 0000000..1e0a07c Binary files /dev/null and b/werewoolf/voicesline/roles/fr/Seer.wav differ diff --git a/werewoolf/voicesline/roles/fr/Werewolf.wav b/werewoolf/voicesline/roles/fr/Werewolf.wav new file mode 100644 index 0000000..e098cf8 Binary files /dev/null and b/werewoolf/voicesline/roles/fr/Werewolf.wav differ diff --git a/werewoolf/voicesline/roles/fr/Witch.wav b/werewoolf/voicesline/roles/fr/Witch.wav new file mode 100644 index 0000000..4ac115f Binary files /dev/null and b/werewoolf/voicesline/roles/fr/Witch.wav differ