<reponame>VergilTheHuragok/Python-Greek-Text-Adventure
"""
Start the actual Game
"""
import time
import random

from functools import reduce

from core.configs import get_value
from core.display import Text
from core.files import get_files
from core.save import get_saves_path, save_to_file, load_from_file, rename_file
from core.tools import weighted_choice, cap_all_words
from game_objects import dict_to_attack, LOCATIONS_LIST, WEAPONS_LIST, EVERYTHING_LIST, all_attacks, NPC, \
    ambrosia, starting_health, treasure, cerastes, gold_ingot, spartae, minotaur,\
    bestiary, anthology, cook_book, finely_cooked_meal, cooked_meal, mead, atlas, monks_dogs, dagger, hrunting, \
    hrothgars_gift, grendel, adamantine_blade \

# TODO: Store lines out of screen past configurable char limit in variables

header_color = get_value("customization", "header_color")
header_size = get_value("customization", "header_size")
number_color = get_value("customization", "number_color")
prompt_color = get_value("customization", "prompt_color")
input_color = get_value("customization", "input_color")

path_font_size = get_value("customization", "path_font_size")
path_font = get_value("customization", "path_font")
choice_font = get_value("customization", "choice_font")
choice_font_size = get_value("customization", "choice_font_size")

location_color = get_value("game_customization", "location_color")
weapon_color = get_value("game_customization", "weapon_color")
attack_color = get_value("game_customization", "attack_color")
inventory_color = get_value("game_customization", "inventory_color")
npc_color = get_value("game_customization", "npc_color")
dead_font = get_value("game_customization", "dead_font")
dead_font_size = get_value("game_customization", "dead_font_size")
dead_color = get_value("game_customization", "dead_color")
default_color = get_value("display", "default_color")
multiattack_color = get_value("game_customization", "multiattack_color")
cooldown_color = get_value("game_customization", "cooldown_color")
self_color = get_value("game_customization", "self_color")
treasure_color = get_value("game_customization", "treasure_color")

starting_health = get_value("game_objects", "starting_health")

extension = get_value("save", "extension")

tutorials = get_value("tutorials")


# Game Object Classes

class GameAction(object):
    """Stores object traits

    :type text_list: None | list[Text] | str
    """

    def __init__(self, text_list=None):
        if isinstance(text_list, str):
            text_list = [Text(text_list)]
        self.text_list = text_list

    def set_text(self, text):
        old_text = self.get_text_object()
        old_text.set_text(text)
        old_text.update_whole()
        self.text_list = [old_text]

    def set_text_list(self, text_list):
        self.text_list = text_list

    def get_text_string(self):
        """Returns entire text list as a single string"""
        if isinstance(self.text_list, type(None)):
            raise Exception("Text should not be None")
        string = ""
        for text in self.text_list:
            string += text.get_whole()
        return string

    def get_text_object(self):
        """Returns Text object with intact attributes and all text from list"""
        if isinstance(self.text_list, type(None)):
            raise Exception("Text should not be None")
        string = ""
        for text in self.text_list:
            string += text.get_text()
        text_object = self.text_list[0].copy(True)
        text_object.set_text(string)
        return text_object

    def get_text_list(self):
        text_list = []
        for text in self.text_list:
            text_list.append(text.copy(True))
        return text_list


class GameObject(GameAction):
    """Stores information related to in-game actions

    :type text_list: None | list[Text] | str
    :type _actions: list[GameAction]
    :type _objects: list[GameObjects]
    """

    def __init__(self, text_list=None, _objects=None, _actions=None,
                 attributes=None):
        GameAction.__init__(self, text_list)
        self.contained_objects = _objects
        self.contained_actions = _actions
        if isinstance(self.contained_objects, type(None)):
            self.contained_objects = []
        if isinstance(self.contained_actions, type(None)):
            self.contained_actions = []

        if isinstance(attributes, type(None)):
            self.attributes = {}
        else:
            self.attributes = attributes
        self.tooltip = self.get_tooltip()

    def copy(self):
        new_obj = GameObject(self.text_list, self.contained_objects,
                             self.contained_actions, self.attributes)
        return new_obj

    def show_attacks_desc(self):
        if not isinstance(self.text_list, type(None)):
            for text in self.text_list:
                text.set_tooltip(self.tooltip)

    def hide_attacks_desc(self):
        if not isinstance(self.text_list, type(None)):
            self.tooltip = self.get_tooltip()
            for text in self.text_list:
                text.set_tooltip([self.get_tooltip()[0]])

    def update_tooltip(self):
        """Adds objects and actions to the tooltip"""

        for text in self.text_list:
            text.tooltip.append(Text("", None, new_line=True))
            text.tooltip.append(Text("", None, new_line=True))

            text.tooltip.append(
                Text("Objects:", None, new_line=True, color=header_color,
                     bold=True))
            for _object in self.contained_objects:
                text.tooltip.append(_object.get_text_list()[0])
                text.tooltip.append(Text(" "))

            text.tooltip.append(Text("", None, new_line=True))
            text.tooltip.append(Text("", None, new_line=True))

            text.tooltip.append(
                Text("Actions:", None, new_line=True, color=header_color,
                     bold=True))
            for action in self.contained_actions:
                text.tooltip.append(action.get_text_list()[0])
                text.tooltip.append(Text(" "))

    def set_tooltip(self, tooltip):
        for text in self.text_list:
            text.tooltip = tooltip

    def get_tooltip(self):
        for text in self.text_list:
            if not isinstance(text.tooltip, type(None)):
                return text.tooltip

    def add_action(self, action):
        self.contained_actions.append(action)

    def set_actions(self, actions):
        self.contained_actions = actions

    def clear_actions(self):
        self.contained_actions = []

    def get_actions(self):
        return self.contained_actions

    def add_object(self, _object):
        self.contained_objects.append(_object)

    def remove_object(self, _object):
        self.contained_objects.remove(_object)

    def remove_action(self, _action):
        self.contained_actions.remove(_action)

    def add_objects(self, _objects):
        for _object in _objects:
            self.contained_objects.append(_object)

    def clear_objects(self):
        self.contained_objects = []

    def get_objects(self):
        return self.contained_objects

    def get_object_names(self):
        names = []
        for _object in self.contained_objects:
            names.append(_object.get_text_string())
        return names

    def get_action_names(self):
        names = []
        for _action in self.contained_actions:
            names.append(_action.get_text_string())
        return names

    def find_object(self, _name):
        for _object in self.contained_objects:
            if _object.get_text_string().strip() == _name:
                return _object

    def find_action(self, _name):
        for _action in self.contained_actions:
            if _action.get_text_string().strip() == _name:
                return _action

    def add_attribute(self, key, value):
        self.attributes[key] = value

    def remove_attribute(self, key):
        self.attributes.pop(key)

    def get_attribute(self, key):
        return self.attributes[key]

    def get_attributes(self):
        return self.attributes


# Save Classes

class GameSave(object):
    """Stores all game values"""

    def __init__(self, name="", add_event=None):
        self.name = name
        self.cryptic = False
        self.temp_cryptic = False
        self.camo = False
        self.camo_plus = False
        self.orpheus = False
        self.shield = False
        self.wolf = False
        self.fleece = False
        self.sack = False
        self.sleep = 0
        self.dead = False
        self.path = [
            GameObject([Text("Game", color=input_color)],
                       [
                           GameObject(
                               [Text("Inventory", color=inventory_color)]),
                           GameObject(
                               [Text("Surroundings", color=inventory_color)]),
                           GameObject(
                               [Text("Stats", color=inventory_color)],
                               attributes={
                                   "Character": NPC("yourself"),
                               },
                           ),
                           GameObject(
                               [Text("Attacks", color=inventory_color)],
                           ),
                       ]
                       )
        ]
        if isinstance(add_event, type(None)):
            raise Exception("Game Save object needs add_event")
        else:
            self.add_event = add_event
        self.path[0].find_object("Stats").add_object(
            GameObject([self.get_you_word(tooltip=[Text("")], strip=True)],
                       _actions=[GameAction([Text("Use")])]))
        self.update_weapons()
        self.update_attacks(True)
        # Equip first attack by default
        self.equip_default()
        self.current_objects = None  # For showing/hiding objects
        self.update_stats_tooltip()
        self.new_location = True
        self.name_nums = {}
        self.type_nums = {}
        self.uniques = []

        self.boosts = []
        self.accurate = 0
        self.viewed_npc = None
        self.herot = False

        self.hotkey_action = None
        self.current_path = None

    def notify_switch(self):
        self.add_event([Text("Equipped attack not available, switched to ",
                             color=prompt_color), Text(
            game_save.get_equipped().get_attribute("Attack").name + ".",
            tooltip=game_save.get_equipped().get_attribute("Attack").gen_desc(),
            new_line=True)])

    def update_attacks(self, first=False):
        """Clears actions and adds starting actions"""
        self.path[0].find_object("Attacks").clear_objects()
        current_attacks = []
        for attack in self.get_character().get_attacks():
            if attack.get_name() not in current_attacks:
                if attack.name not in current_attacks:
                    if attack.current_cooldown > 0:
                        attack_color = cooldown_color
                    elif attack.current_cooldown < 0:
                        attack_color = multiattack_color
                    elif "Self" in attack.attributes:
                        attack_color = self_color
                    else:
                        attack_color = default_color
                    self.path[0].find_object("Attacks").add_object(GameObject(
                        [Text(attack.get_name(), tooltip=attack.gen_desc(
                            None, False), color=attack_color)],
                        _actions=[GameAction("Equip")],
                        attributes={"Attack": attack}))
                    current_attacks.append(attack.get_name())

        for weapon in self.path[0].find_object("Inventory").get_objects():
            for attack in weapon.get_attribute("Item").get_attacks():
                if "Cryptic" in attack.get_attributes() and not self.cryptic:
                    continue
                attack_name = weapon.get_attribute(
                    "Item").get_name() + ":" + attack.get_name()
                if attack_name not in current_attacks:
                    if attack.current_cooldown > 0:
                        attack_color = cooldown_color
                    elif attack.current_cooldown < 0:
                        attack_color = multiattack_color
                    elif "Self" in attack.attributes:
                        attack_color = self_color
                    else:
                        attack_color = default_color

                    flower = False
                    if "Flower" in weapon.get_attribute("Item"). \
                            get_attributes() and not self.cryptic:
                        flower = True

                    self.path[0].find_object("Attacks").add_object(GameObject(
                        [Text(attack_name, tooltip=attack.gen_desc(
                            weapon.get_attribute("Item"), False, flower=flower),
                              color=attack_color)],
                        _actions=[GameAction("Equip")],
                        attributes={"Attack": attack, "Weapon": weapon}))
                    current_attacks.append(attack_name)

        inv = list([weap.get_attribute("Item") for weap in
                    self.get_path()[0].find_object(
                        "Inventory").get_objects()])
        equipped = True
        if "Equipped" not in self.path[0].find_object(
                "Attacks").get_attributes():
            equipped = False
        found = True
        if equipped and "Weapon" in self.get_equipped().get_attributes():
            weapon = self.get_equipped().get_attribute("Weapon").get_attribute(
                "Item")
            attack = self.get_equipped().get_attribute("Attack")
            if weapon not in inv:  # If weapon is lost, set all identical weapons to same cooldown for continued multiuse
                found = False
                for weap in inv:  # Update all cooldowns of same weapons/attacks
                    if weap.get_name() == weapon.get_name():
                        found = True
                        for atta in weap.get_attacks():
                            if atta.get_name() == attack.get_name():
                                atta.current_cooldown = attack.current_cooldown
                        weapon = weap
                if found:
                    for atta_obj in self.get_path()[0].find_object(
                            "Attacks").get_objects():  # Find same weapon/attack to equip instead
                        temp_atta = atta_obj.get_attribute("Attack")
                        if temp_atta.get_name() == attack.get_name():
                            if "Weapon" in atta_obj.get_attributes():
                                if atta_obj.get_attribute(
                                        "Weapon").get_attribute(
                                    "Item").get_name() == weapon.get_name():
                                    # Same attack name, same weapon name
                                    if temp_atta != attack:
                                        # Different attack
                                        self.path[0].find_object(
                                            "Attacks").add_attribute("Equipped",
                                                                     atta_obj)
                                        self.update_attacks_tooltip()
                                        break

        if not equipped or not found:

            self.equip_default()
            if not first:
                self.open_path()
                self.notify_switch()
                return True
        return False

    def open_path(self, pth="Attacks"):
        # pth = cap_all_words(pth, [">", "-", ":"])
        if pth == "Game":
            self.set_path_index(0)
        elif pth == "Back":
            if len(self.get_path()) > 1:
                self.remove_path_index(-1)
        else:
            self.set_path_index(0)
            for step in pth.split(">"):
                if step in self.get_path()[-1].get_object_names():
                    self.append_path(self.get_path()[-1].find_object(step))
                elif step in self.get_path()[-1].get_action_names():
                    return self.get_path()[-1].find_action(step)

    def update_attacks_tooltip(self):
        text_list = [Text("Equipped Attack", color=header_color,
                          font_size=header_size, new_line=True),
                     Text("", new_line=True)]
        attack = self.get_equipped().get_attribute("Attack")
        if "Weapon" in self.get_equipped().get_attributes():
            weapon = self.get_equipped().get_attribute("Weapon").get_attribute(
                "Item")
        else:
            weapon = None

        flower = False
        if not isinstance(weapon, type(None)):
            if "Flower" in weapon.get_attributes() and not self.cryptic:
                flower = True

        text_list += attack.gen_desc(weapon, flower=flower)
        self.get_path()[0].find_object("Attacks").set_tooltip(text_list)

    def rename_flowers(self):
        flowers = []

        for item in self.get_path()[0].find_object("Inventory").get_objects():
            flowers.append(item)

        for item in self.get_path()[0].find_object(
                "Surroundings").get_objects():
            flowers.append(item)

        for item in flowers:
            if "Item" in item.get_attributes():
                flower_item = item.get_attribute("Item")
                if "Flower" in flower_item.get_attributes():
                    if not self.cryptic:
                        new_name = flower_item.get_attributes()["Flower"]
                        if new_name != "Herb" and new_name != "Shirt-of-Nessus":
                            new_name += "-Flower"
                        item.hide_attacks_desc()
                    else:
                        new_name = flower_item.get_type()
                        item.show_attacks_desc()
                    flower_item.set_name(new_name)
                    item.set_text(new_name)

    def equip_default(self):
        self.path[0].find_object("Attacks").add_attribute(
            "Equipped", self.path[0].find_object(
                "Attacks").get_objects()[0])
        self.update_attacks_tooltip()

    def get_equipped(self):
        return self.get_path()[0].find_object("Attacks").get_attribute(
            "Equipped")

    def update_weapons(self):
        """Clears the inventory and adds starting weapons"""
        self.path[0].find_object("Inventory").clear_objects()
        for weapon in self.get_character().get_weapons():
            if "Treasure" in weapon.get_attributes():
                color = treasure_color
            else:
                color = weapon_color
            self.path[0].find_object("Inventory").add_object(GameObject(
                [Text(weapon.get_name(), color=color,
                      tooltip=weapon.gen_desc())], attributes={
                    "Item": weapon,
                }, _actions=[GameAction("Drop")]))

    def get_character(self):
        return self.path[0].find_object("Stats").get_attribute(
            "Character")

    def append_path(self, _object):
        self.path.append(_object)

    def remove_path_index(self, ind):
        self.path.pop(ind)

    def add_health_boost(self, healths, turns, name_word):
        """Additive effect"""
        self.boosts.append(
            {"Healths": healths, "Turns": turns, "Teller": name_word})
        self.boost_health(len(self.boosts) - 1, True)

    def add_attack_boost(self, attacks, turns, name_word):
        """Multiplicative effect"""
        self.boosts.append(
            {"Attacks": attacks, "Turns": turns, "Teller": name_word})

    def boost_health(self, ind, on=True):
        current_boost = self.boosts[ind]
        health = self.get_health().copy()
        for stat in current_boost["Healths"]:
            if stat not in health:
                health[stat] = 0
            if on:
                health[stat] += current_boost["Healths"][stat]
            else:
                health[stat] -= (current_boost["Healths"][stat] - 1)
        self.set_health(health)

    def boost_attacks(self):
        attacks_dict = {}
        for attacks in list(filter(lambda x: "Attacks" in x, self.boosts)):
            for attack in attacks["Attacks"]:
                if attack not in attacks_dict:
                    attacks_dict[attack] = 0
                attacks_dict[attack] += attacks["Attacks"][attack]
        return attacks_dict

    def check_boosts(self):
        if self.accurate > 0:
            self.accurate -= 1
            if self.accurate == 0:
                self.add_event(
                    [Text("The effects of hearing ", color=prompt_color),
                     self.accurate_word,
                     Text("'s story have worn off.", color=prompt_color,
                          new_line=True)])
        ind = 0
        for boost in self.boosts:
            boost["Turns"] -= 1
            if boost["Turns"] <= 0:
                if "Healths" in boost:
                    self.boost_health(ind, False)
                self.add_event(
                    [Text("The effects of hearing ", color=prompt_color),
                     boost["Teller"],
                     Text("'s story have worn off.", color=prompt_color,
                          new_line=True)])
                self.boosts.pop(ind)
            ind += 1

    def canterbury(self, npc, treasures):
        name_parts = npc.get_name().split("-")
        if name_parts[-1].isnumeric():
            name_parts.pop()
        name = reduce(lambda x, y: x + "-" + y, name_parts)

        if not name == "The-Franklin":
            for item in treasures:
                npc.weapons.append(item.get_attribute("Item"))

        desc = None

        if name == "The-Knight":
            desc = "inspires you to persevere through pain."
            self.add_health_boost({"Sharp": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Squire":
            desc = "inspires you to stay strong."
            self.add_health_boost({"Brute": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Plowman":
            desc = "inspires you to be more righteous."
            self.add_health_boost({"Divine": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Reeve":
            desc = "inspires you to be quick on your feet."
            self.add_health_boost({"Agility": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Franklin":
            desc = "inspires you to be friendlier."
            self.add_health_boost({"Charisma": 1}, 30,
                                  npc.get_word(True, True))
            self.add_event([npc.get_word(True, False),
                            Text("gave you back your payment.",
                                 color=prompt_color, new_line=True)])
            for _treasure in treasures:
                self.add_inv_item(_treasure)
        elif name == "The-Sailor":
            desc = "inspires you to be one with nature."
            self.add_attack_boost({"Tame": 2}, 30,
                                  npc.get_word(True, True))
            bestiary_word = Text(bestiary.get_name(), color=weapon_color,
                                 tooltip=bestiary.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 bestiary_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([bestiary_word],
                                         attributes={"Item": bestiary}))
        elif name == "The-Parson":
            desc = "inspires you to spread the word of God."
            self.add_attack_boost({"Divine": 2}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Clerk":
            anthology_word = Text(anthology.get_name(), color=weapon_color,
                                  tooltip=anthology.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 anthology_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([anthology_word],
                                         attributes={"Item": anthology}))
        elif name == "The-Cook":
            cook_word = Text(cook_book.get_name(), color=weapon_color,
                             tooltip=cook_book.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 cook_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([cook_word],
                                         attributes={"Item": cook_book}))
        elif name == "The-Wife-of-Bath":
            atlas_word = Text(atlas.get_name(), color=weapon_color,
                              tooltip=atlas.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 atlas_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([atlas_word],
                                         attributes={"Item": atlas}))
        elif name == "The-Yeoman":
            desc = "inspires you to focus in combat."
            self.accurate = 30
            self.accurate_word = npc.get_word(True, True)
        elif name == "The-Monk":
            game_save.gen_npc(monks_dogs)
            game_save.gen_npc(monks_dogs)
            self.add_event(
                [npc.get_word(True, True),
                 Text("'s dogs attack you while he tells you his story.",
                      color=prompt_color, new_line=True)])
        elif name == "The-Friar":
            health = self.get_health()
            health["Divine"] = 1
            self.set_health(health)
            self.add_event([npc.get_word(True),
                            Text("absolves your sins.", color=prompt_color,
                                 new_line=True)])
        elif name == "The-Merchant":
            success, items = self.get_payment(1, True)
            for item in items:
                npc.weapons.append(item.get_attribute("Item"))
        elif name == "The-Lawyer":
            success, items = self.get_payment(-1, True)
            for item in items:
                npc.weapons.append(item.get_attribute("Item"))

        if not isinstance(desc, type(None)):
            self.add_event([npc.get_word(True, True), Text("'s story ",
                                                           color=prompt_color),
                            Text(desc, color=prompt_color, new_line=True)])
        self.update_surroundings()

    def get_payment(self, num, trick=False):
        treasures = []
        treasure_words = []
        for _object in self.get_path()[0].find_object(
                "Inventory").get_objects():
            item = _object.get_attribute("Item")
            if "Treasure" in item.get_attributes():
                treasures.append(_object)
                treasure_words.append(
                    Text(item.get_name(), color=treasure_color,
                         tooltip=item.gen_desc()))
                if len(treasures) == num:
                    break
        if len(treasures) == num or num == -1:
            treasure_string = []
            i = 0
            for word in treasure_words:
                if i == 0:
                    treasure_string.append(word)
                elif i == len(treasure_words) - 1:
                    treasure_string.append(
                        Text(", and ", color=prompt_color))
                    treasure_string.append(word)
                else:
                    treasure_string.append(
                        Text(", ", color=prompt_color))
                    treasure_string.append(word)
                i += 1

            if trick:
                sentence = "He takes your "
            else:
                sentence = "You hand over "
            self.add_event(
                [Text(sentence, color=prompt_color),
                 *treasure_string,
                 Text(".", color=prompt_color,
                      new_line=True)])
            for item in treasures:
                self.remove_inv_item(item)

            return True, treasures
        else:
            if not trick:
                self.add_event([Text("You don't have enough treasure.",
                                     color=prompt_color, new_line=True)])
            return False, treasures

    def drop_empty(self, weapon_object, attack_object):
        self.add_event([Text("Equipped attack is out of ammo.", color=prompt_color,
                        new_line=True)])
        weapon_object = attack_object.get_attribute("Weapon")
        self.remove_inv_item(weapon_object)
        self.update_surroundings()

    def add_inv_item(self, item):
        self.path[0].find_object("Inventory").add_object(item)
        self.path[0].find_object("Inventory").get_objects()[-1].set_actions(
            [
                GameAction("Drop")])
        health = self.get_health()
        health["Agility"] -= 5
        self.set_health(health)  # Reduce agility

    def remove_inv_item(self, item):
        if item not in self.path[0].find_object("Inventory").get_objects():
            for _item in self.path[0].find_object(
                    "Inventory").get_objects():
                if item.get_text_string() == _item.get_text_string():
                    self.path[0].find_object("Inventory").remove_object(
                        _item)
                    break
        else:
            self.path[0].find_object("Inventory").remove_object(item)
        health = self.get_health()
        health["Agility"] += 5
        self.set_health(health)  # Increase agility

    def add_ground_item(self, item, npc=False):
        self.path[0].find_object("Surroundings").add_object(item)
        if not npc:
            self.path[0].find_object("Surroundings").get_objects()[
                -1].set_actions([GameAction("Pick-up")])

    def remove_ground_item(self, item):
        self.path[0].find_object("Surroundings").remove_object(item)

    def add_main_action(self, action):
        self.path[0].add_action(action)

    def clear_main_actions(self):
        self.path[0].clear_actions()

    def hide_objects(self):
        self.path[0].set_text_list([Text("Menu", color=input_color,
                                         font_size=path_font_size,
                                         font_name=path_font)])
        self.current_objects = self.path[-1].get_objects()[:]
        self.path[-1].clear_objects()

    def show_objects(self):
        self.path[0].set_text_list([Text("Game", color=input_color,
                                         font_size=path_font_size,
                                         font_name=path_font)])
        self.path[-1].add_objects(self.current_objects)
        self.current_objects = None

    def set_path_index(self, ind):
        self.path = self.path[:ind + 1]

    def get_path(self):
        return self.path

    def get_path_text_list(self, string=False):
        path_text_list = []
        for _object in self.get_path():
            if not string:
                path_obj = _object.get_text_object()
                path_obj.set_font(choice_font, choice_font_size)
                path_text_list.append(path_obj)
            else:
                path_text_list.append(_object.get_text_string())
            if type(_object) == GameObject:
                if not string:
                    path_text_list.append(Text(">", color=input_color,
                                               font_size=choice_font_size,
                                               font_name=choice_font))
                else:
                    path_text_list.append(">")
        if string:
            return "".join(path_text_list)
        return path_text_list

    def check_caught(self):
        """Check if cannot leave"""
        for npc in self.get_npcs():
            if npc.target == "You":
                if random.randint(0, npc.get_health()["Agility"]) > \
                        self.get_health()["Agility"]:
                    return npc  # You were caught
        return False

    def gen_location(self, location_name=None):
        """Chooses a random location to spawn in"""

        self.path[0].find_object("Surroundings").clear_objects()
        self.path[0].find_object("Surroundings").clear_actions()

        available_locations = list(filter(lambda
                                              x: x.get_name() != "Strange-Cavern" and x.get_name() != "Treasure-Hall",
                                          LOCATIONS_LIST))
        if "Grendel" in self.uniques:
            available_locations = list(
                filter(lambda x: x.get_name() != "Treasure-Hall",
                       LOCATIONS_LIST))
        if "Grendel's-Mother" in self.uniques:
            available_locations = LOCATIONS_LIST

        probabilities = [l.probability for l in available_locations]
        if isinstance(location_name, type(None)):
            location = weighted_choice(available_locations, probabilities)
        else:  # Given name, find location
            location = None
            for _location in available_locations:
                if _location.get_type() == location_name:
                    location = _location
                    break
        location.gen_name()
        self.path[0].find_object("Surroundings").add_attribute(
            "location", location)

        self.path[0].find_object("Surroundings").add_action(GameAction(
            "Leave-Location"))
        self.path[0].find_object("Surroundings").add_action(GameAction(
            "Wait"))
        self.path[0].find_object("Surroundings").set_tooltip(
            [Text("Location", color=header_color, bold=True, new_line=True,
                  font_size=header_size),
             Text(location.get_name(), new_line=True),
             Text("", new_line=True),
             *location.gen_desc()])
        # Gen items
        objects = []
        for _weapon in location.gen_objects(location.weapons):
            objects.append(GameObject([Text(_weapon.get_name(),
                                            color=weapon_color,
                                            tooltip=_weapon.gen_desc())],
                                      _actions=[GameAction("Pick-up")],
                                      attributes={"Item": _weapon}))

        tmax = location.treasure["Max"]
        tmin = location.treasure["Min"]
        tprob = location.treasure["Probability"]
        treasures = []
        if random.randint(0, 100) <= tprob:
            for i in range(0, random.randint(tmin, tmax)):
                treasure_item = treasure.copy(False)
                treasures.append(GameObject([Text(
                    treasure_item.get_name(), color=treasure_color,
                    tooltip=treasure_item.gen_desc())], _actions=[
                    GameAction("Pick-up")], attributes={"Treasure": True,
                                                        "Item": treasure_item}))

        self.gen_npcs()
        if location.get_name() == "Herot":
            if self.herot:
                self.gen_npc(grendel)
        self.path[0].find_object("Surroundings").add_objects(objects)
        self.path[0].find_object("Surroundings").add_objects(treasures)
        self.new_location = True
        self.rename_flowers()
        self.reset_cooldowns()

    def gen_npcs(self, wander=False):
        if not wander:
            self.name_nums = {}
            self.type_nums = {}
        # Gen NPCs
        location = self.get_path()[0].find_object(
            "Surroundings").get_attribute("location")
        if "Boss" not in location.get_attributes():
            npc_word_list = []
            npc_amounts = {}
            for npc in location.gen_objects(location.npcs, wander, self.type_nums):
                if self.gen_npc(npc):
                    npc = self.get_path()[0].find_object(
                        "Surroundings").get_objects()[-1].get_attribute(
                        "NPC")
                    npc_word_list.append(npc.get_word())
            return npc_word_list
        elif not wander:
            # Only spawn one random creature
            npcs = location.gen_objects(location.npcs)
            living_npcs = []
            for npc in npcs:
                if "Unique" in npc.get_attributes():
                    if npc.get_type() in self.uniques:
                        # Already been killed
                        continue
                living_npcs.append(npc)
            while len(living_npcs) > 0:
                if self.gen_npc(random.choice(living_npcs)):
                    npc = self.get_path()[0].find_object(
                        "Surroundings").get_objects()[-1].get_attribute(
                        "NPC")
                    return [npc.get_word()]
        return []

    def gen_npc(self, _npc, new_name=True):
        """Creates an npc and adds to surroundings"""
        npc = _npc.copy()
        if npc.get_type() in self.uniques and new_name:
            return False
        if new_name:
            npc.gen_name(self.name_nums, self.type_nums)
        else:
            npc.determine_number(self.name_nums, self.type_nums)

        weapon_objects = []
        for _weapon in npc.get_weapons():
            if "Treasure" in _weapon.get_attributes():
                color = treasure_color
            else:
                color = weapon_color
            weapon_objects.append(GameObject([
                Text(_weapon.get_name(), color=color,
                     tooltip=_weapon.gen_desc())],
                attributes={"Item": _weapon}))

        actions = [GameAction("Attack")]
        if "Story-Teller" in npc.get_attributes():
            if isinstance(npc.target, type(None)):
                actions.append(GameAction([Text("Pay", color=treasure_color,
                                                new_line=True)]))
        if "Hrothgar" in npc.get_attributes() and isinstance(npc.target,
                                                             type(None)):
            actions.append(GameAction([Text("Talk", new_line=True)]))

        self.add_ground_item(
            GameObject([npc.get_word(False)], _actions=actions,
                       _objects=weapon_objects,
                       attributes={"NPC": npc}), npc=True)
        return True

    def get_npcs(self):
        """Returns NPCs in current surroundings"""
        npcs = []
        for _object in self.path[0].find_object(
                "Surroundings").get_objects():
            if "NPC" in _object.get_attributes():
                npcs.append(_object.get_attribute("NPC"))

        return npcs

    def get_health(self):
        """Return Health Stats of self"""
        return self.get_character().get_health()

    def set_health(self, stats):
        """Sets the health stats for self"""
        self.get_character().set_health(stats)
        self.update_stats_tooltip()

    def update_stats_tooltip(self):
        """Updates stats tooltip with current info"""
        desc = [Text("Health", color=header_color, bold=True,
                     new_line=True, font_size=header_size)]
        for stat in self.get_health():
            desc += [Text(stat + ": ", color=header_color),
                     Text(str(self.get_health()[stat]), new_line=True,
                          color=number_color)]
        self.path[0].find_object("Stats").set_tooltip(desc)
        self.path[0].find_object("Stats").find_object("You").set_tooltip(
            desc)

    def get_you_word(self, caps=True, tooltip=None, strip=False,
                     yourself=False):
        if caps:
            word = "You "
        else:
            word = "you "
        if yourself:
            word = "yourself"
        if strip:
            word = word.strip()
        if isinstance(tooltip, type(None)):
            tooltip = self.get_stats_tooltip()[:]
        return Text(word, tooltip=tooltip,
                    color=self_color)

    def get_stats_tooltip(self):
        return self.path[0].find_object("Stats").get_tooltip()

    def update_surroundings(self):
        self.viewed_npc = None
        if "NPC" in self.get_path()[-1].get_attributes():
            self.viewed_npc = self.get_path()[-1].get_attribute(
                "NPC").get_name()
        in_surroundings = False
        if self.path[-1] == self.path[0].find_object("Surroundings"):
            self.remove_path_index(-1)
            in_surroundings = True
        new_objects = []
        new_npcs = []
        for _object in self.path[0].find_object(
                "Surroundings").get_objects():
            if "NPC" not in _object.get_attributes():
                new_objects.append(_object)
            else:
                new_npcs.append(_object.get_attribute("NPC"))
        self.path[0].find_object("Surroundings").clear_objects()
        for npc in new_npcs:
            self.gen_npc(npc, new_name=False)
        self.path[0].find_object("Surroundings").add_objects(new_objects)
        if in_surroundings:
            self.open_path("Surroundings")
        if not isinstance(self.viewed_npc, type(None)):
            self.remove_path_index(-1)
            self.append_path(
                self.get_path()[0].find_object(
                    "Surroundings").find_object(self.viewed_npc))

    def reset_cooldowns(self):
        for attack in self.get_path()[0].find_object(
                "Attacks").get_objects():
            attack_item = attack.get_attribute("Attack")
            attack_item.reset_cooldown()

    def tick(self):
        for attack in self.get_path()[0].find_object(
                "Attacks").get_objects():
            attack_item = attack.get_attribute("Attack")
            if attack_item.tick():
                weapon = None
                flower = False
                if "Weapon" in attack.get_attributes():
                    weapon = attack.get_attribute("Weapon").get_attribute(
                        "Item")
                    if "Flower" in weapon.get_attributes() and not self.cryptic:
                        flower = True
                self.add_event([Text(attack.get_text_string() + " ",
                                     tooltip=attack_item.gen_desc(weapon,
                                                                  False,
                                                                  None,
                                                                  flower)),
                                Text("is ready to use.", color=prompt_color,
                                     new_line=True)])

    def check_combat(self):
        current_living_npcs = self.get_npcs()[:]
        while len(current_living_npcs) > 0:
            _npc = current_living_npcs.pop(0)
            if "Chained" in _npc.attributes:
                continue
            attack = None
            weapon = None
            while True:
                if not isinstance(weapon,
                                  type(None)) and weapon not in _npc.weapons:
                    found = False
                    for weap in _npc.weapons:
                        if weap.get_name() == weapon.get_name():
                            for atta in weap.get_attacks():
                                if atta.get_name() == attack.get_name():
                                    atta.current_cooldown = attack.current_cooldown
                                    break
                            weapon = weap
                            found = True
                            break  # Found same type of weapon, allow continued attack
                    if not found:
                        weapon = None
                        break  # End multiattack turn because weapon was lost
                targets = []
                npc_teams = []
                if "Team" in _npc.get_attributes():
                    npc_teams = _npc.get_attribute("Team")
                for _target in self.get_npcs():
                    if _target != _npc:
                        target_teams = []
                        if "Team" in _target.get_attributes():
                            target_teams = _target.get_attribute("Team")
                        if "All" in target_teams:
                            # Target is a friend to all
                            continue
                        elif len(list(set(target_teams) & set(
                                npc_teams))) > 0:
                            # Shares a team
                            continue
                        elif "All" in npc_teams and not ("You" in target_teams):
                            continue  # Do not add enemy to teams unless attacking a teammate of player's
                        if _target.get_type() == _npc.get_type():
                            if "Cannibal" in _npc.get_attributes():
                                targets.append(_target)
                        else:
                            targets.append(_target)

                your_team = "You" in npc_teams
                if not self.check_if_dead() and (not your_team or _npc.target == "You"):
                    targets.append("You")
                target = _npc.get_target(targets, self.camo, self.camo_plus)

                # Find target based on chosen target name
                for _target in targets:
                    if (_target != "You" and _target.get_name() == target) \
                            or target == _target:
                        target = _target
                        break
                if not isinstance(target, type(None)):
                    if target == "You":
                        health = self.get_health()
                    else:
                        health = target.get_health()
                    stats, damages, attack, weapon, multiattack = _npc.attack(
                        health, attack, weapon)
                    if target != "You":
                        target.target = _npc.get_name()
                    if not isinstance(attack, type(None)):
                        if "Thrown" in attack.get_attributes():
                            self.path[0].find_object(
                                "Surroundings").add_object(
                                GameObject([Text(weapon.get_name(),
                                                 color=weapon_color,
                                                 tooltip=weapon.gen_desc())],
                                           _actions=[
                                               GameAction("Pick-up")],
                                           attributes={"Item": weapon}))
                            _npc.weapons.remove(weapon)
                        elif "Consumable" in attack.get_attributes():
                            _npc.weapons.remove(weapon)
                        elif "Disarm" in attack.get_attributes():
                            if target == "You":
                                if "Weapon" in self.get_equipped(). \
                                        get_attributes():
                                    item = self.get_equipped().get_attribute(
                                        "Weapon")
                                    self.add_ground_item(
                                        item)  # Add to ground
                                    self.remove_inv_item(
                                        item)  # Remove from inventory
                                    item = item.get_attribute("Item")
                                    self.add_event([self.get_you_word(),
                                                    Text("dropped "),
                                                    Text(item.get_name(),
                                                         color=weapon_color,
                                                         tooltip=item.gen_desc(),
                                                         new_line=True)])
                            else:
                                if len(target.weapons) > 0:
                                    # Remove random weapon
                                    disarmed_weapon = random.choice(
                                        _npc.weapons)
                                    _npc.weapons.remove(disarmed_weapon)
                                    self.get_path()[0].find_object(
                                        "Surroundings").add_object(
                                        GameObject(
                                            [Text(
                                                disarmed_weapon.get_name(),
                                                color=weapon_color, tooltip=
                                                disarmed_weapon.gen_desc())],
                                            attributes={
                                                "Item": disarmed_weapon},
                                            _actions=[
                                                GameAction("Pick-up")]))
                                    self.add_event(
                                        [_npc.get_word(), Text("dropped "),
                                         Text(
                                             disarmed_weapon.get_name(),
                                             color=weapon_color,
                                             tooltip=disarmed_weapon.gen_desc(),
                                             new_line=True)])
                        elif "Sleep+" in attack.get_attributes():
                            if target == "You":
                                if not game_save.orpheus:
                                    game_save.sleep = 10
                                    self.add_event(
                                        [Text("You have fallen into a deep sleep.",
                                              new_line=True, color=prompt_color)])
                                elif "Sleep+" in attack.get_attributes() and game_save.orpheus:
                                    game_save.orpheus = False
                                    self.add_event([Text(
                                        "The music of Orpheus\\'s Lyre keeps you alert. Its effects have now worn off.",
                                        new_line=True, color=prompt_color)])
                        elif "Wolf" in attack.get_attributes():
                            if target == "You":
                                game_save.wolf = True
                        if target == "You":
                            self.set_health(stats)
                        else:
                            target.set_health(stats)
                        is_dead = False
                        life = False
                        if len(damages) >= 1:
                            # Hit
                            tame = False
                            if target == "You":
                                is_dead = self.check_if_dead()
                            else:
                                is_dead, tame, life = target.check_if_dead()
                            self.hit_event(attack, damages, _npc, weapon,
                                           is_dead, tame, target)
                        else:
                            self.miss_event(attack, damages, _npc, weapon,
                                            target)

                        if target != "You" and is_dead:
                            # Target was killed
                            if "Unique" in target.get_attributes():
                                self.uniques.append(target.get_type())
                            if target in current_living_npcs:
                                current_living_npcs.remove(target)
                            npc_num = self.get_npcs().index(target)
                            self.get_path()[0].find_object(
                                "Surroundings").contained_objects.pop(
                                npc_num)
                            for _weapon in target.get_weapons():
                                if "Retained" not in _weapon.get_attributes() \
                                        and not life:
                                    self.get_path()[0].find_object(
                                        "Surroundings").add_object(
                                        GameObject(
                                            [Text(_weapon.get_name(),
                                                  color=weapon_color,
                                                  tooltip=
                                                  _weapon.gen_desc())],
                                            attributes={
                                                "Item": _weapon},
                                            _actions=[
                                                GameAction("Pick-up")]))
                            self.drop_ambrosia(target)
                            if game_save.viewed_npc == target.get_name():
                                # remove inv of observed npc
                                self.remove_path_index(-1)
                else:
                    break  # No targets
                if not multiattack:
                    _npc.tick()
                    break

        self.update_surroundings()

    def drop_ambrosia(self, target):
        if random.randint(1, 20000) \
                <= target.get_health()["Tame"]:
            self.add_event([Text(
                "Ambrosia dropped to the ground.",
                color=prompt_color, new_line=True)])
            self.get_path()[0].find_object(
                "Surroundings").add_object(GameObject(
                [Text(ambrosia.get_name(),
                      color=weapon_color,
                      tooltip=ambrosia.gen_desc())],
                attributes={
                    "Item": ambrosia,
                }, _actions=[GameAction("Pick-up")]))

    def check_egg(self):
        egg = False
        for weapon in self.get_path()[0].find_object(
                "Inventory").get_objects():
            if "Egg" in weapon.get_attribute("Item").attributes:
                self.remove_inv_item(weapon)
                self.set_health(starting_health)
                self.update_attacks()
                egg = True
        if egg:
            self.add_event([Text("YOU ARE DEAD", color=dead_color,
                                 font_name=dead_font,
                                 font_size=dead_font_size, new_line=True)])
            time.sleep(2)

            self.add_event([Text(
                "You hear a crack.",
                color=prompt_color, new_line=True)])

            time.sleep(2)

            self.add_event([Text("YOU LIVE AGAIN", color=dead_color,
                                 new_line=True, font_size=dead_font_size,
                                 font_name=dead_font)])
        return egg

    def check_if_dead(self):
        return self.get_character().check_if_dead()[0]

    def hit_event(self, _attack, damages, attacker, _weapon,
                  _is_dead=False, tame=False, target=None):

        is_you_target = target == "You" or target.get_name() == "yourself"
        is_you_attacker = attacker == "You" or attacker.get_name() == "yourself"

        if is_you_attacker:
            attacker_word = self.get_you_word()
        else:
            attacker_word = attacker.get_word()
        attack_word = Text(_attack.past_tense + " ",
                           tooltip=self.gen_attack_tooltip(_attack,
                                                           damages),
                           color=attack_color)
        if is_you_target:
            target_word = self.get_you_word(caps=False, strip=True,
                                            yourself=is_you_attacker)
        else:
            target_word = target.get_word(strip=True)
        text_list = [attacker_word, attack_word, target_word]

        flower = False
        if not isinstance(_weapon, type(None)):
            if "Flower" in _weapon.get_attributes() and not self.cryptic:
                flower = True

        if not isinstance(_weapon, type(None)):
            text_list += [Text(" "), Text("with "),
                          Text(_weapon.get_name().replace("-", " "),
                               color=weapon_color,
                               tooltip=_weapon.gen_desc(flower=flower))]
        if tame:
            text_list.append(Text(" "))
            text_list.append(Text("taming them"))
        elif _is_dead and not is_you_target:
            text_list.append(Text(" "))
            text_list.append(Text("killing them"))

        text_list.append(Text(".", new_line=True))
        self.add_event(text_list)

    def miss_event(self, _attack, damages, attacker, _weapon, target):
        is_you_target = target == "You" or target.get_name() == "yourself"
        is_you_attacker = attacker == "You" or attacker.get_name() == "yourself"

        if is_you_attacker:
            attacker_word = self.get_you_word()
        else:
            attacker_word = attacker.get_word()
        attack_word = Text(_attack.name.lower().replace("-", " ") + " ",
                           tooltip=self.gen_attack_tooltip(_attack,
                                                           damages),
                           color=attack_color)
        if is_you_target:
            target_word = self.get_you_word(caps=False, strip=True,
                                            yourself=is_you_attacker)
        else:
            target_word = target.get_word()
        text_list = [attacker_word, Text("tried to "), attack_word,
                     target_word]

        flower = False
        if not isinstance(_weapon, type(None)):
            if "Flower" in _weapon.get_attributes() and not self.cryptic:
                flower = True

        if not isinstance(_weapon, type(None)):
            text_list += [Text(" "), Text("with "),
                          Text(_weapon.get_name().replace("-", " "),
                               color=weapon_color,
                               tooltip=_weapon.gen_desc(flower=flower))]
        text_list.append(Text(" "))
        text_list.append(Text("but missed.", new_line=True))
        self.add_event(text_list)

    @staticmethod
    def gen_attack_tooltip(_attack, damages):
        # Add Attack name to attack tooltip
        desc = [Text(_attack.get_name(), font_size=header_size,
                     color=header_color, new_line=True),
                Text("Current Cooldown: "), Text(str(
                _attack.display_current_cooldown()), color=number_color,
                new_line=True)]
        if len(damages) == 0:
            desc.append(Text("No damage.", new_line=True))
        else:
            # Add attack stats to attack tooltip
            for damage in damages:
                desc += [Text(damage + ": ", color=header_color),
                         Text(str(damages[damage]), new_line=True,
                              color=number_color)]
        return desc

    game_save = None


def start(_get_input, update_textbox, _set_choices, mark_temporary,
          clear_temporary, is_running, set_caption, check_hotkeys,
          disable_hotkeys, enable_hotkeys, set_dead):
    """Function to start gameplay"""
    global game_save

    # TODO: Hotkey's/Shortcuts box?
    # TODO: Choose an object and see its actions

    # Interface functions

    def add_event(_event=""):
        """Adds an event to the events box

        :type _event: str | list[Text]
        :param _event: "" for new line
        """

        if _event == "":
            _event = [Text("", new_line=True)]

        update_textbox("events", _event)

    game_save = GameSave(add_event=add_event)

    def get_input(prompt=None, display_answer=True, allow_path_change=True,
                  constrict=True, hotkeys=False):
        """Creates a prompt and waits for input

        :type prompt: str | None
        :type display_answer: bool
        :type allow_path_change: bool
        :type constrict: bool
        :type hotkeys: bool
        """

        set_choices(constrict)
        if not hotkeys:
            disable_hotkeys()
        user_input, path_changed = _get_input(prompt, game_save.get_path(),
                                              allow_path_change)
        if not hotkeys:
            enable_hotkeys()
        if isinstance(path_changed, type(None)):
            # Main thread quit
            return None, None

        if display_answer:
            add_event([Text("Menu>", color=input_color,
                            font_size=path_font_size, font_name=path_font),
                       Text(user_input, color=input_color,
                            font_size=path_font_size, font_name=path_font)])

        return user_input, path_changed

    def find_choice(_choice):
        """Returns the object or action chosen based on given name

        :type _choice: str
        :rtype: GameObject | GameAction | None
        """

        if _choice == "":
            return

        # Down a path
        current_objects = game_save.get_path()[-1].get_objects()
        current_actions = game_save.get_path()[-1].get_actions()

        for _object in current_objects:
            if _object.get_text_string().strip() == _choice:
                return _object
        for _action in current_actions:
            if _action.get_text_string().strip() == _choice:
                return _action
                # raise Exception("Could not find choice: " + _choice)

    def set_choices(constrict=True):
        """Sets the choices to cycle between"""
        if constrict:
            _set_choices(game_save.get_path()[-1].get_objects(),
                         game_save.get_path()[-1].get_actions())
        else:
            _set_choices([], [])

    def get_from_choices(prompt, choices, display_answer=True):
        game_save.set_path_index(0)
        game_save.hide_objects()
        for choice in choices:
            game_save.add_main_action(GameAction([Text(choice)]))
        _choice = get_choice(prompt, display_answer=display_answer)
        if isinstance(_choice, type(None)):
            save_to_file(game_save, game_save.name + "." + extension)
            raise SystemExit

        game_save.clear_main_actions()
        game_save.show_objects()
        display_objects()
        return _choice

    def select_action(chosen_object, display_answer, return_string):
        if display_answer:
            # Get Path and reduce font size
            path_text_list = []
            for _object in game_save.get_path_text_list():
                object_text = _object.copy(False)
                object_text.set_font(path_font, path_font_size)
                path_text_list.append(object_text)

            # Get chosen object and reduce font size
            object_text = chosen_object.get_text_object().copy(
                False)
            object_text.set_font(path_font, path_font_size)
            path_text_list.append(object_text)

            # Add newline and add to events
            path_text_list[-1].new_line = True
            add_event(path_text_list)

        if return_string:
            return chosen_object.get_text_string().strip()
        return chosen_object

    def get_choice(prompt=None, display_answer=True, return_string=True,
                   allow_path_change=True, choose_action=False, hotkeys=False):
        """Gets user input based on current objects/actions

        :type prompt: None | str
        :type display_answer: bool
        :type return_string: bool
        :type allow_path_change: bool
        :type choose_action: bool
        """

        if not isinstance(prompt, type(None)):  # Display Prompt
            add_event([Text(prompt, color=prompt_color, new_line=True)])

        display_objects()
        chosen_object, back = get_input(display_answer=False,
                                        allow_path_change=allow_path_change,
                                        hotkeys=hotkeys)
        if isinstance(back, type(None)):
            # Main thread quit
            return None

        if back == len(game_save.get_path()) - 1:
            # Path was not changed

            chosen_object = find_choice(chosen_object)
            if type(chosen_object) == GameObject:
                game_save.append_path(chosen_object)
            if type(chosen_object) == GameAction or not choose_action:

                return select_action(chosen_object, display_answer,
                                     return_string)

            return get_choice(None, display_answer, return_string,
                              allow_path_change, choose_action, hotkeys)

        elif back == "HOTKEY":
            current_path = game_save.get_path_text_list(True)
            action = game_save.open_path(chosen_object)
            if not isinstance(action, type(None)):
                game_save.current_path = current_path
                return select_action(action, display_answer, return_string)
            display_objects()
            return get_choice(None, display_answer, return_string,
                              allow_path_change, choose_action, hotkeys)
        else:
            # Path moved back via delete
            if len(game_save.get_path()) > 1:
                game_save.set_path_index(back)
            display_objects()
            return get_choice(None, display_answer, return_string,
                              allow_path_change, choose_action, hotkeys)

    def get_yes(prompt=None, display_answer=True, choices=None):
        """Returns the users response to a yes/no question

        :type prompt: None | str
        :type display_answer: bool
        :type choices: None | list[str]
        :param choices: Provide two choices in a list. 'Yes'/'No' if None
        """
        if not isinstance(prompt, type(None)):
            add_event(
                [Text(prompt, None, color=prompt_color, new_line=True)])

        if isinstance(choices, type(None)):
            choices = ["Yes", "No"]

        if len(choices) > 2:
            raise Exception("Too many choices provided")

        game_save.set_path_index(0)
        game_save.hide_objects()
        game_save.add_main_action(GameAction([Text(choices[0])]))
        game_save.add_main_action(GameAction([Text(choices[1])]))

        _choice = get_choice(display_answer=display_answer)
        if isinstance(_choice, type(None)):
            raise SystemExit

        game_save.clear_main_actions()
        game_save.show_objects()
        display_objects()

        if _choice == choices[0]:
            return True
        if _choice == choices[1]:
            return False
        raise Exception("Non yes/no answer given to yes/no question")

    def continue_prompt():
        """Prompts the user to press 'Enter'"""
        add_event([Text("Press 'Enter' to continue...", new_line=True,
                        color=input_color)])

        game_save.set_path_index(0)

        game_save.hide_objects()

        display_objects()
        while True:
            user_input = get_input(display_answer=False,
                                   allow_path_change=False)
            if isinstance(user_input[1], type(None)):
                raise SystemExit
            elif user_input[1] != "HOTKEY":
                break

        game_save.show_objects()

        display_objects()

    def display_objects():
        """Sends the current objects to the gui"""

        set_choices()

        # Object selected
        current_objects = game_save.get_path()[-1].get_objects()
        current_actions = game_save.get_path()[-1].get_actions()

        if len(current_objects) < 1:  # Clear box if empty
            update_textbox("objects", "", True)
        if len(current_actions) < 1:
            update_textbox("actions", "", True)

        first = True  # Clear textbox on first word
        i = 1
        for _object in current_objects:
            comma = ""
            if i < len(current_objects):
                comma = ","
            text = _object.get_text_list()[:]
            text[-1].strip()
            text[-1].append(comma)
            text[-1].append(" ")
            update_textbox("objects", text, first)
            first = False
            i += 1

        first = True  # Clear textbox on first word
        i = 1
        for _action in current_actions:
            comma = ""
            if i < len(current_actions):
                comma = ","
            text_list = _action.get_text_list()
            text_list[-1].strip()
            text_list[-1].append(comma)
            text_list[-1].append(" ")
            update_textbox("actions", text_list, first)
            first = False
            i += 1

        # Display path

        update_textbox("path", game_save.get_path_text_list(), True)

    # Game Functions

    def tutorial():
        """Displays the tutorial"""
        # TODO: Update. Add Gameplay
        if get_yes("Do you want to view a tutorial? "
                   "(Use 'Tab' and 'Enter' to select an option)",
                   choices=["View", "Skip"]):

            prompt = "What would you like to learn about?"

            while True:

                _choice = get_from_choices(prompt, list(tutorials) + ["Cancel"])

                if _choice != "Cancel":
                    for line in tutorials[_choice]:
                        add_event([Text(line, new_line=True)])
                        continue_prompt()
                        add_event()
                    add_event()
                else:
                    break

    def save_and_load():
        """Save or load a game"""

        global game_save

        saves = list(filter(lambda x: x.endswith("." + extension),
                            get_files(
                                get_saves_path())))  # Get all save files
        if len(saves) < 1 or get_yes("Would you like to start a new game?",
                                     choices=["New", "Load"]):
            game_save.hide_objects()
            display_objects()

            name, _exit = get_input(
                "What do you want to name your game save?",
                constrict=False)
            if isinstance(_exit, type(None)):
                raise SystemExit

            game_save.show_objects()
            display_objects()

            game_save = GameSave(name, add_event=add_event)
            game_save.gen_location("Town")
            save_to_file(game_save, name + "." + extension)
        else:
            game_save.set_path_index(0)
            game_save.hide_objects()

            for file in saves:
                game_save.add_main_action(GameAction(
                    file[:-len(extension) - 1]))

            save_file = get_choice("Choose a save file.")
            if isinstance(save_file, type(None)):
                raise SystemExit
            save_file += "." + extension

            game_save.clear_main_actions()
            game_save.show_objects()

            game_save = load_from_file(save_file)
            game_save.set_path_index(0)
            game_save.add_event = add_event
            display_objects()
        set_caption("The Odyssey+: " + game_save.name)

    def display_location():
        location = game_save.get_path()[0].find_object(
            "Surroundings").get_attribute("location")
        add_event(
            [game_save.get_you_word(), Text("are in "),
             Text(location.get_name(), color=location_color,
                  tooltip=location.gen_desc()), Text(".", new_line=True)])

    # Start game

    tutorial()

    save_and_load()

    add_event()

    # Spawn Location
    if not game_save.dead:
        display_location()

        # Main game loop
        alive = not game_save.check_if_dead()
    else:
        alive = False
    while alive:  # Main game loop
        save_to_file(game_save, game_save.name + "." + extension)

        multiattack = False
        # Combat

        game_save.update_attacks()

        if game_save.check_if_dead():
            if not game_save.check_egg():
                break  # Died, break loop

        game_save.check_combat()

        game_save.check_boosts()

        if game_save.check_if_dead():
            if not game_save.check_egg():
                break  # Died, break loop

        game_save.update_attacks()

        choosing = True

        # Choices
        if game_save.sleep > 0:
            game_save.sleep -= 1
            if game_save.sleep == 0:
                add_event([Text("You awaken.", color=prompt_color,
                                new_line=True)])

        if game_save.temp_cryptic:
            if game_save.cryptic:
                game_save.temp_cryptic = False
                game_save.cryptic = False
            else:
                game_save.cryptic = True
            game_save.rename_flowers()

        weapon = None

        while (choosing or multiattack) and game_save.sleep == 0:  # Main choice loop
            cancel = False
            reset_cooldowns = False
            if not isinstance(game_save.current_path, type(None)):
                game_save.open_path(game_save.current_path)
                game_save.current_path = None
            choice = get_choice(return_string=True, choose_action=True,
                                hotkeys=True)
            if isinstance(choice, type(None)):
                # Game exit
                break

            if game_save.fleece:
                if game_save.get_health()["Brute"] < 10000:
                    game_save.get_health()["Brute"] += 200
                if game_save.get_health()["Sharp"] < 10000:
                    game_save.get_health()["Sharp"] += 200
                if game_save.get_health()["Burn"] < 10000:
                    game_save.get_health()["Burn"] += 200
                if game_save.get_health()["Freeze"] < 10000:
                    game_save.get_health()["Freeze"] += 200
                if game_save.get_health()["Divine"] < 10000:
                    game_save.get_health()["Divine"] += 300
                if game_save.get_health()["Agility"] < 1000:
                    game_save.get_health()["Agility"] += 50
                game_save.update_stats_tooltip()

            if choice == "Pick-up":
                if not multiattack:
                    multiattack = False
                    item = game_save.get_path()[-1]

                    too_heavy = False
                    if "Heavy" in item.get_attribute(
                                    "Item").get_attributes():
                        too_heavy = game_save.get_health()["Brute"] < 10000
                    if not too_heavy:
                        if game_save.get_path()[0].find_object(
                                "Surroundings").get_attribute(
                                "location").get_type() != "Town":
                            success = True
                        else:
                            success, treasures = game_save.get_payment(1, False)
                        if success:
                            if "Spawn-Cerastes" in item.get_attribute(
                                    "Item").get_attributes():
                                game_save.gen_npc(cerastes)
                                add_event([Text("You pull a ", color=dead_color),
                                           cerastes.get_word(),
                                           Text("out of the sand.",
                                                color=dead_color,
                                                new_line=True)])
                            elif "Smite" in item.get_attribute(
                                    "Item").get_attributes():
                                add_event([Text(
                                    "Zeus smote you with a lightning bolt.",
                                    color=dead_color, new_line=True)])
                                game_save.get_health()["Divine"] = 0
                                if not game_save.check_egg():
                                    break  # Died, break loop
                            elif "Sack" in item.get_attribute("Item").get_attributes():
                                game_save.sack = True
                            elif "Head" in item.get_attribute("Item").get_attributes():
                                if not game_save.sack:
                                    add_event([Text(
                                        "Medusa\'s eyes stare into your soul.",
                                        color=dead_color, new_line=True)])
                                    game_save.get_health()["Soul"] = 0
                                    if not game_save.check_egg():
                                        break  # Died, break loop
                                else:
                                    add_event([Text(
                                        "You pick up the head with the Kibisis.",
                                        color=prompt_color, new_line=True)])
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                            elif "Shield" in item.get_attribute("Item").get_attributes():
                                if not game_save.shield:
                                    game_save.shield = True
                                    game_save.get_health()["Brute"] += 100000
                                    game_save.get_health()["Sharp"] += 100000
                                    game_save.get_health()["Burn"] += 100000
                                    game_save.get_health()["Agility"] -= 100
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                                    game_save.update_stats_tooltip()
                                else:
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                            elif "Fleece" in item.get_attribute("Item").get_attributes():
                                if not game_save.fleece:
                                    game_save.fleece = True
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                                else:
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                            else:
                                item.get_attribute("Item").reset_cooldowns()
                                game_save.add_inv_item(item)  # Add to inventory
                            game_save.remove_ground_item(item)  # Remove from ground
                            game_save.remove_path_index(-1)  # Remove from Path
                            choosing = False
                    else:
                        add_event([Text("The item is too heavy.",
                                        color=prompt_color, new_line=True)])
                else:
                    add_event([Text("You have not finished your attack.",
                                    color=prompt_color, new_line=True)])

            elif choice == "Drop":
                multiattack = False
                item = game_save.get_path()[-1]
                item.get_attribute("Item").reset_cooldowns()
                if "Fleece" in item.get_attribute("Item").get_attributes():
                    game_save.fleece = False
                game_save.add_ground_item(item)  # Add to ground
                game_save.remove_inv_item(item)  # Remove from inventory
                game_save.remove_path_index(-1)  # Remove from Path

            elif choice == "Leave-Location":
                multiattack = False
                monster = game_save.check_caught()

                location = game_save.get_path()[0].find_object("Surroundings").\
                    get_attribute("location")

                if monster:  # Objects eval to True
                    # Was caught
                    add_event([monster.get_word(), Text("caught "),
                               game_save.get_you_word(False, strip=True),
                               Text(".", new_line=True)])
                    choosing = False
                else:
                    # Escaped
                    if location.get_name() == "Labyrinth":
                        if random.randint(0, 20) == 20:
                            game_save.gen_location()
                            add_event([Text(
                                "You escaped the Labyrinth.",
                                color=prompt_color, new_line=True)])
                        else:
                            game_save.gen_location("Labyrinth")
                    else:
                        game_save.gen_location()
                    display_location()

                if "Jumpscare" in location.get_attributes():
                    choosing = False

            elif choice == "Wait":
                multiattack = False
                choosing = False

            elif choice == "Equip":
                equipped_attack = game_save.get_path()[0].find_object(
                    "Attacks").get_attribute("Equipped").get_attribute(
                    "Attack")
                if equipped_attack.current_cooldown <= 0 or "Cooldown" \
                        in equipped_attack.get_attributes():
                    game_save.get_path()[0].find_object("Attacks"). \
                        add_attribute("Equipped", game_save.get_path()[-1])
                    game_save.update_attacks_tooltip()
                    if "Self" in game_save.get_path()[-1].get_attribute(
                            "Attack").get_attributes():
                        game_save.set_path_index(0)
                        stats = game_save.get_path()[0].find_object("Stats")
                        game_save.get_path().append(stats)
                        game_save.get_path().append(
                            stats.find_object("You"))
                    else:
                        game_save.set_path_index(0)
                        game_save.append_path(
                            game_save.get_path()[0].find_object(
                                "Surroundings"))
                    if multiattack:  # If multiattacking, end turn
                        multiattack = False
                        choosing = False
                else:
                    add_event(
                        [Text("Cannot change weapons while equipped is "
                              "cooling down.", color=prompt_color,
                              new_line=True)])
            elif choice == "Talk":
                npc = game_save.get_path()[-1].get_attribute(
                    "NPC")
                if not multiattack:
                    item = GameObject(
                        [Text(hrunting.get_name(),
                              color=weapon_color,
                              tooltip=hrunting.gen_desc())],
                        attributes={"Item": hrunting,
                                    }, _actions=[GameAction("Drop")])
                    game_save.add_inv_item(item)
                    add_event([Text("Hrothgar gives you Hrunting on behalf of Unferth.",
                                    color=prompt_color, new_line=True)])
                    add_event([npc.get_word(), Text(
                        "says, 'The holy Creator usward sent him, To West-Dane warriors, I ween, for to render ’Gainst Grendel’s grimness gracious assistance: I shall give to the good one gift-gems for courage.'",
                        color=prompt_color, new_line=True)])
                    game_save.herot = True
                    game_save.uniques.append(npc.get_type())
                    game_save.get_path()[-1].remove_action(
                        game_save.get_path()[-1].get_actions()[1])
                else:
                    add_event([Text("You have not finished your attack.",
                                    color=prompt_color, new_line=True)])
            elif choice == "Pay":
                npc = game_save.get_path()[-1].get_attribute(
                    "NPC")
                if not multiattack:
                    success, treasures = game_save.get_payment(1)
                    if success:
                        npc.paid = True
                        game_save.canterbury(
                            npc, treasures)
                        multiattack = False
                        choosing = False
                else:
                    add_event([Text("You have not finished your attack.",
                                    color=prompt_color, new_line=True)])

            elif choice == "Attack" or choice == "Use":
                disarmed_weapon = None
                power = False
                # Get equipped attack
                attack_object = game_save.get_path()[0].find_object(
                    "Attacks").get_attribute("Equipped")
                # Get Attack from object
                attack = attack_object.get_attribute("Attack")

                # Check if attack has cooldown
                if attack.ammo > 0 and attack.current_ammo < 1:
                    multiattack = False
                    game_save.drop_empty(weapon_object, attack_object)
                elif attack.current_cooldown > 0:
                    add_event(
                        [Text("Equipped attack is still cooling down.",
                              color=prompt_color, new_line=True)])
                    game_save.open_path("Surroundings")
                else:
                    # Get target npc
                    npc_num = 0
                    is_you = False
                    if game_save.get_path()[-1].get_text_string() == "You":
                        _npc = game_save.get_character()
                        is_you = True
                    else:
                        if "Self" in attack.get_attributes():
                            add_event(
                                [Text("Can only be used on self.",
                                      color=prompt_color, new_line=True)])
                            game_save.set_path_index(0)
                            stats = game_save.get_path()[0].find_object(
                                "Stats")
                            game_save.get_path().append(stats)
                            game_save.get_path().append(
                                stats.find_object("You"))
                            continue
                        _npc = game_save.get_path()[-1].get_attribute("NPC")
                        for npc in game_save.get_path()[0].find_object(
                                "Surroundings").get_objects():
                            if "NPC" in npc.get_attributes():
                                npc_name = npc.get_attribute(
                                    "NPC").get_name()
                                if npc_name == _npc.get_name():
                                    _npc = npc.get_attribute("NPC")
                                    break
                            npc_num += 1
                    stats, _damages = attack.attack(_npc.get_health(), is_you,
                                                    game_save.boost_attacks(),
                                                    game_save.accurate)

                    taming = False
                    for _damage in _damages:
                        if _damages[_damage] > 0:
                            if _damage == "Tame" or _damage == "Tame+" or _damage == "Tame2+":
                                taming = True
                    if not taming:
                        _npc.target = "You"
                    # Update npc health
                    _npc.set_health(stats)
                    # Update npc tooltip
                    if is_you:
                        game_save.update_stats_tooltip()
                    else:
                        game_save.get_path()[-1].set_tooltip(
                            _npc.gen_desc())

                    if "Disarm" in attack.get_attributes():
                        if len(_npc.weapons) > 0:
                            disarmed_weapon = random.choice(
                                list(filter(lambda x: "Body" not in
                                                      x.get_attributes(),
                                            _npc.weapons)))
                            _npc.weapons.remove(disarmed_weapon)
                            disarmed_weapon.reset_cooldowns()
                            game_save.get_path()[0].find_object(
                                "Surroundings").add_object(GameObject(
                                [Text(disarmed_weapon.get_name(),
                                      color=weapon_color,
                                      tooltip=disarmed_weapon.gen_desc())],
                                attributes={
                                    "Item": disarmed_weapon,
                                }, _actions=[GameAction("Pick-up")]))
                    if attack.ammo > 0 and attack.current_ammo < 1:
                        multiattack = False
                        game_save.drop_empty(weapon_object, attack_object)
                    if "Weapon" in attack_object.get_attributes():

                        weapon_object = attack_object.get_attribute(
                            "Weapon")
                        weapon = weapon_object.get_attribute("Item")
                        if "Thrown" in attack.get_attributes():
                            reset_cooldowns = True
                            game_save.add_ground_item(weapon_object)
                            game_save.remove_inv_item(weapon_object)
                        elif "Consumable" in attack.get_attributes():
                            game_save.remove_inv_item(weapon_object)
                    else:
                        weapon = None
                    if "Speed" in attack.get_attributes():
                        power = True
                        # Allow reduce cooldown weapons
                        for _weapon in _npc.weapons:
                            for _attack in _weapon.attacks:
                                _attack.decrease_cooldown()
                        for _attack in _npc.attacks:
                            _attack.decrease_cooldown()
                    if "Cryptic+" in attack.get_attributes():
                        power = True
                        game_save.cryptic = True
                        game_save.temp_cryptic = False
                        game_save.rename_flowers()
                    if "Orpheus" in attack.get_attributes():
                        power = True
                        game_save.orpheus = True
                    if "Calm" in attack.get_attributes():
                        power = True
                        _npc.target = None
                        _npc.aggressiveness /= 2
                    if "Enrage" in attack.get_attributes():
                        power = True
                        _npc.target = None
                        for _attack in _npc.attacks:
                            _attack *= 2
                    if "Herbal" in attack.get_attributes():
                        if not game_save.cryptic:
                            game_save.temp_cryptic = True
                            power = True
                        else:
                            add_event([Text("You already have this knowledge.",
                                            color=prompt_color, new_line=True)])
                            cancel = True
                    if "Camo" in attack.get_attributes():
                        power = True
                        game_save.camo = True
                    if "Camo+" in attack.get_attributes():
                        power = True
                        game_save.camo_plus = True
                    if "Trumpet" in attack.get_attributes():
                        power = True
                        npc_word_list = game_save.gen_npcs(
                            True)  # Allow for wandering monsters
                        for npc_word in npc_word_list:
                        #   if not isinstance(npc_word, type(None)):
                            add_event([npc_word, Text("wandered into the vicinity at the call of the trumpet.",
                                                            new_line=True,
                                                            color=treasure_color)])
                    if "Atlas" in attack.get_attributes():
                        location_choices = []
                        probabilities = [l.probability for l in LOCATIONS_LIST]
                        while True:
                            location = weighted_choice(LOCATIONS_LIST,
                                                       probabilities)
                            if location not in location_choices:
                                location_choices.append(location)
                            if len(location_choices) == 5:
                                break
                        choice = get_from_choices(
                            "Choose a location to travel to.", list(
                                location.get_type() for location in list(
                                    filter(lambda x: x.get_type() != "Olympus",
                                           location_choices))))
                        game_save.gen_location(choice)
                        display_location()
                    if "Sleep" in attack.get_attributes() and not game_save.orpheus:
                        game_save.sleep = 5
                        power = True
                        add_event([Text("You have fallen asleep.",
                                        new_line=True, color=prompt_color)])
                    elif "Sleep" in attack.get_attributes() and game_save.orpheus:
                        game_save.orpheus = False
                        add_event([Text("The music of Orpheus\\'s Lyre keeps you alert. Its effects have now worn off.",
                                        new_line=True, color=prompt_color)])
                    if "Omphalos" in attack.get_attributes():
                        game_save.gen_location("Olympus")
                        display_location()
                    if "Travel" in attack.get_attributes():
                        game_save.gen_location("Town")
                        display_location()
                    if "Thread" in attack.get_attributes():
                        game_save.gen_location("Labyrinth")
                        display_location()
                    if "Chains" in attack.get_attributes():
                        power = True
                        if "Chained" not in _npc.get_attributes():
                            _npc.add_attribute("Chained", True)
                            add_event([Text("Unbreakable chains wrap around them "
                                            "and they can't move.", new_line=True,
                                            color=prompt_color)])
                            if "Fox" in _npc.get_attributes():
                                _npc.set_health(starting_health)
                    if "Sleep+" in attack.get_attributes() and not game_save.orpheus:
                        game_save.sleep = 10
                        power = True
                        add_event(
                            [Text("You have fallen into a deep sleep.",
                                  new_line=True, color=prompt_color)])
                    if "Rod" in attack.get_attributes():
                        if game_save.get_health()["Brute"] < 3000:
                            game_save.get_health()["Brute"] += 100
                        if game_save.get_health()["Sharp"] < 2000:
                            game_save.get_health()["Sharp"] += 100
                        if game_save.get_health()["Burn"] < 1000:
                            game_save.get_health()["Burn"] += 100
                        if game_save.get_health()["Freeze"] < 1500:
                            game_save.get_health()["Freeze"] += 100
                        if game_save.get_health()["Agility"] < 400:
                            game_save.get_health()["Agility"] += 50
                        game_save.update_stats_tooltip()
                    elif "Sleep+" in attack.get_attributes() and game_save.orpheus:
                        game_save.orpheus = False
                        add_event([Text("The music of Orpheus\\'s Lyre keeps you alert. Its effects have now worn off.",
                                        new_line=True, color=prompt_color)])
                    if "Spawn-Weapon" in attack.get_attributes():
                        power = True
                        random_weapon = random.choice(WEAPONS_LIST)
                        random_weapon = GameObject(
                            [Text(random_weapon.get_name(),
                                  color=weapon_color,
                                  tooltip=random_weapon.gen_desc())],
                            attributes={
                                "Item": random_weapon,
                            }, _actions=[GameAction("Pick-Up")])
                        game_save.add_ground_item(random_weapon)
                        game_save.rename_flowers()
                 #   if "Spawn-Attack" in attack.get_attributes():
                  #      power = True
                   #     random_attack = dict_to_attack(random.choice(all_attacks))
                    #    game_save.get_character().add_attacks(
                     #       random_attack)
                    if "Spawn-Finely-Cooked-Meal" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(finely_cooked_meal.get_name(),
                                  color=weapon_color,
                                  tooltip=finely_cooked_meal.gen_desc())],
                            attributes={"Item": finely_cooked_meal,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You created a finely cooked meal.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Meal" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(cooked_meal.get_name(),
                                  color=weapon_color,
                                  tooltip=cooked_meal.gen_desc())],
                            attributes={"Item": cooked_meal,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You created a cooked meal.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Mead2" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(mead.get_name(),
                                  color=weapon_color,
                                  tooltip=mead.gen_desc())],
                            attributes={"Item": mead,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        game_save.add_inv_item(item)
                        add_event([Text("You obtained some mead.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Dagger" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(dagger.get_name(),
                                  color=weapon_color,
                                  tooltip=dagger.gen_desc())],
                            attributes={"Item": dagger,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You unsheath a dagger.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Treasure" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(gold_ingot.get_name(),
                                  color=treasure_color,
                                  tooltip=gold_ingot.gen_desc())],
                            attributes={'Treasure': True,
                                        "Item": gold_ingot,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You create a gold ingot.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Anything" in attack.get_attributes():
                        power = True
                        random_object = random.choice(EVERYTHING_LIST)
                        if type(random_object) != NPC:
                            random_object = GameObject(
                                [Text(random_object.get_name(),
                                      color=weapon_color,
                                      tooltip=random_object.gen_desc())],
                                attributes={
                                    "Item": random_object,
                                }, _actions=[GameAction("Pick-Up")])
                            game_save.add_ground_item(random_object)
                            game_save.rename_flowers()
                        else:
                            game_save.gen_npc(random_object)
                    if "Spawn-Spartae" in attack.get_attributes():
                        power = True
                        game_save.gen_npc(spartae)
                    if "Cow" in attack.get_attributes():
                        power = True
                        game_save.gen_npc(minotaur)
                    if "Self" in attack.get_attributes():
                        power = True

                    is_dead, tame, life = _npc.check_if_dead()

                    if len(_damages) >= 1 or power:
                        # Hit
                        game_save.hit_event(attack, _damages, "You", weapon,
                                            is_dead, tame, _npc)
                    else:
                        game_save.miss_event(attack, _damages, "You",
                                             weapon,
                                             _npc)

                    if not isinstance(disarmed_weapon, type(None)):
                        add_event([_npc.get_word(), Text("dropped "),
                                   Text(disarmed_weapon.get_name(),
                                        color=weapon_color,
                                        tooltip=disarmed_weapon.gen_desc(),
                                        new_line=True)])

                    if is_dead and not is_you:
                        if isinstance(_npc.attributes, type(None)):
                            _npc.attributes = []
                        if "Unique" in _npc.get_attributes():
                            game_save.uniques.append(_npc.get_type())
                        if "Smite" in _npc.attributes and not tame:
                            add_event([Text(
                                "Zeus smote you with a lightning bolt.",
                                color=dead_color, new_line=True)])
                            game_save.get_health()["Divine"] = 0
                            if not game_save.check_egg():
                                break  # Died, break loop
                        if "Grendel" in _npc.attributes and not tame:
                            item = GameObject(
                                [Text(hrothgars_gift.get_name(),
                                      color=weapon_color,
                                      tooltip=hrothgars_gift.gen_desc())],
                                attributes={"Item": hrothgars_gift,
                                            }, _actions=[GameAction("Drop")])
                            game_save.add_inv_item(item)
                            add_event([Text(
                                "Hrothgar is pleased. He presents you with a beautiful blade.",
                                color=prompt_color, new_line=True)])
                        if "Ophiotaurus" in _npc.attributes and not tame and _npc.get_health()["Burn"] <= 0:
                            item = GameObject(
                                [Text(adamantine_blade.get_name(),
                                      color=weapon_color,
                                      tooltip=adamantine_blade.gen_desc())],
                                attributes={"Item": adamantine_blade,
                                            }, _actions=[GameAction("Drop")])
                            game_save.add_inv_item(item)
                        for weapon in game_save.get_path()[0].find_object(
                                "Surroundings").get_objects()[
                            npc_num].get_objects():
                            weapon_item = weapon.get_attribute("Item")
                            if "Retained" not in weapon_item.get_attributes() \
                                    and not life and not tame:
                                # Dont drop if killed with life, tame, or is retained
                                weapon.add_action(GameAction("Pick-up"))
                                weapon_item.reset_cooldowns()
                                game_save.get_path()[0].find_object(
                                    "Surroundings").add_object(weapon)

                        current_npc = game_save.get_path()[0].find_object(
                            "Surroundings").get_objects()[npc_num]

                        if tame:
                            game_save.get_character().add_attacks(
                                current_npc.get_attribute("NPC").get_attacks(
                                    True))
                        else:
                            game_save.drop_ambrosia(_npc)

                        # Remove npc
                        game_save.get_path()[0].find_object("Surroundings"). \
                            remove_object(current_npc)
                        game_save.set_path_index(
                            len(game_save.get_path()) - 2)
                    else:
                        # Was not killed
                        if not is_you:
                            if "Fox" in _npc.get_attributes() and "Chained" not in _npc.get_attributes():
                                # Fox flees if attacked
                                add_event([_npc.get_word(),
                                           Text("fled.", color=dead_color,
                                                new_line=True)])

                                game_save.get_path()[0].find_object(
                                    "Surroundings").remove_object(
                                    game_save.get_path()[
                                        -1])
                                game_save.set_path_index(
                                    len(game_save.get_path()) - 2)

                    game_save.update_surroundings()
                    if attack.current_cooldown < 0:
                        multiattack = True
                    if attack.cooldown < 0 and attack.current_cooldown == 0:
                        attack.current_cooldown = attack.cooldown
                        multiattack = False
                    if attack.current_cooldown > 0:
                        game_save.open_path("Surroundings")
                        add_event([Text("Must wait ", color=prompt_color),
                                   Text(str(
                                       attack.current_cooldown - 1) + " ",
                                        color=number_color), Text(
                                "more turns until attack is ready to be used.",
                                new_line=True, color=prompt_color)])
                    if not cancel:
                        choosing = False

            if game_save.update_attacks():
                multiattack = False

            if reset_cooldowns:
                attack.reset_cooldown()

        if not multiattack and not cancel:
            game_save.tick()

        if not game_save.new_location:
            npc_word_list = game_save.gen_npcs(
                True)  # Allow for wandering monsters
            for npc_word in npc_word_list:
                if not isinstance(npc_word, type(None)):
                    add_event([npc_word, Text("wandered into the vicinity.",
                                              new_line=True,
                                              color=dead_color)])
        game_save.new_location = False

        display_objects()
        time.sleep(.01)
        if not is_running():
            break

    file_name = game_save.name + "." + extension
    if is_running():
        add_event(
            [Text("YOU ARE DEAD", color=dead_color, font_name=dead_font,
                  font_size=dead_font_size, new_line=True)])

        # TODO: Add boss fight
        game_save.set_path_index(0)
        game_save.get_path()[0].clear_objects()
        game_save.get_path()[0].clear_actions()
        display_objects()
        set_dead()

        if not game_save.dead:
            rename_file(file_name)
        game_save.dead = True
        save_to_file(game_save, "dead - " + file_name)
    else:
        save_to_file(game_save, file_name)

    # TODO: 'AOE' att on attacks: Area of effect
    # TODO: Display worn items ('Worn' Attribute)
    # TODO: Buy with treasure at towns
    # TODO: Read/Write stories with book
    # TODO: '?' att on weapon chooses random attack to use
    # TODO: Allow use straight from ground/inventory for self items
    # TODO: Add multiplayer
    # TODO: River Stix makes you invincible except for one random health
    # TODO: 'Unique' att on items

# TODO: Auto switch to same type of weapon if one runs out of ammo
# TODO: Make ambrosia yellow, flowers/rocks green
# TODO: Create actual initiative system. Integrate with wandering as well
"""
Start the actual Game
"""
import time
import random

from functools import reduce

from core.configs import get_value
from core.display import Text
from core.files import get_files
from core.save import get_saves_path, save_to_file, load_from_file, rename_file
from core.tools import weighted_choice, cap_all_words
from game_objects import dict_to_attack, LOCATIONS_LIST, WEAPONS_LIST, EVERYTHING_LIST, all_attacks, NPC, \
    ambrosia, starting_health, treasure, cerastes, gold_ingot, spartae, minotaur,\
    bestiary, anthology, cook_book, finely_cooked_meal, cooked_meal, mead, atlas, monks_dogs, dagger, hrunting, \
    hrothgars_gift, grendel, adamantine_blade \

# TODO: Store lines out of screen past configurable char limit in variables

header_color = get_value("customization", "header_color")
header_size = get_value("customization", "header_size")
number_color = get_value("customization", "number_color")
prompt_color = get_value("customization", "prompt_color")
input_color = get_value("customization", "input_color")

path_font_size = get_value("customization", "path_font_size")
path_font = get_value("customization", "path_font")
choice_font = get_value("customization", "choice_font")
choice_font_size = get_value("customization", "choice_font_size")

location_color = get_value("game_customization", "location_color")
weapon_color = get_value("game_customization", "weapon_color")
attack_color = get_value("game_customization", "attack_color")
inventory_color = get_value("game_customization", "inventory_color")
npc_color = get_value("game_customization", "npc_color")
dead_font = get_value("game_customization", "dead_font")
dead_font_size = get_value("game_customization", "dead_font_size")
dead_color = get_value("game_customization", "dead_color")
default_color = get_value("display", "default_color")
multiattack_color = get_value("game_customization", "multiattack_color")
cooldown_color = get_value("game_customization", "cooldown_color")
self_color = get_value("game_customization", "self_color")
treasure_color = get_value("game_customization", "treasure_color")

starting_health = get_value("game_objects", "starting_health")

extension = get_value("save", "extension")

tutorials = get_value("tutorials")


# Game Object Classes

class GameAction(object):
    """Stores object traits

    :type text_list: None | list[Text] | str
    """

    def __init__(self, text_list=None):
        if isinstance(text_list, str):
            text_list = [Text(text_list)]
        self.text_list = text_list

    def set_text(self, text):
        old_text = self.get_text_object()
        old_text.set_text(text)
        old_text.update_whole()
        self.text_list = [old_text]

    def set_text_list(self, text_list):
        self.text_list = text_list

    def get_text_string(self):
        """Returns entire text list as a single string"""
        if isinstance(self.text_list, type(None)):
            raise Exception("Text should not be None")
        string = ""
        for text in self.text_list:
            string += text.get_whole()
        return string

    def get_text_object(self):
        """Returns Text object with intact attributes and all text from list"""
        if isinstance(self.text_list, type(None)):
            raise Exception("Text should not be None")
        string = ""
        for text in self.text_list:
            string += text.get_text()
        text_object = self.text_list[0].copy(True)
        text_object.set_text(string)
        return text_object

    def get_text_list(self):
        text_list = []
        for text in self.text_list:
            text_list.append(text.copy(True))
        return text_list


class GameObject(GameAction):
    """Stores information related to in-game actions

    :type text_list: None | list[Text] | str
    :type _actions: list[GameAction]
    :type _objects: list[GameObjects]
    """

    def __init__(self, text_list=None, _objects=None, _actions=None,
                 attributes=None):
        GameAction.__init__(self, text_list)
        self.contained_objects = _objects
        self.contained_actions = _actions
        if isinstance(self.contained_objects, type(None)):
            self.contained_objects = []
        if isinstance(self.contained_actions, type(None)):
            self.contained_actions = []

        if isinstance(attributes, type(None)):
            self.attributes = {}
        else:
            self.attributes = attributes
        self.tooltip = self.get_tooltip()

    def copy(self):
        new_obj = GameObject(self.text_list, self.contained_objects,
                             self.contained_actions, self.attributes)
        return new_obj

    def show_attacks_desc(self):
        if not isinstance(self.text_list, type(None)):
            for text in self.text_list:
                text.set_tooltip(self.tooltip)

    def hide_attacks_desc(self):
        if not isinstance(self.text_list, type(None)):
            self.tooltip = self.get_tooltip()
            for text in self.text_list:
                text.set_tooltip([self.get_tooltip()[0]])

    def update_tooltip(self):
        """Adds objects and actions to the tooltip"""

        for text in self.text_list:
            text.tooltip.append(Text("", None, new_line=True))
            text.tooltip.append(Text("", None, new_line=True))

            text.tooltip.append(
                Text("Objects:", None, new_line=True, color=header_color,
                     bold=True))
            for _object in self.contained_objects:
                text.tooltip.append(_object.get_text_list()[0])
                text.tooltip.append(Text(" "))

            text.tooltip.append(Text("", None, new_line=True))
            text.tooltip.append(Text("", None, new_line=True))

            text.tooltip.append(
                Text("Actions:", None, new_line=True, color=header_color,
                     bold=True))
            for action in self.contained_actions:
                text.tooltip.append(action.get_text_list()[0])
                text.tooltip.append(Text(" "))

    def set_tooltip(self, tooltip):
        for text in self.text_list:
            text.tooltip = tooltip

    def get_tooltip(self):
        for text in self.text_list:
            if not isinstance(text.tooltip, type(None)):
                return text.tooltip

    def add_action(self, action):
        self.contained_actions.append(action)

    def set_actions(self, actions):
        self.contained_actions = actions

    def clear_actions(self):
        self.contained_actions = []

    def get_actions(self):
        return self.contained_actions

    def add_object(self, _object):
        self.contained_objects.append(_object)

    def remove_object(self, _object):
        self.contained_objects.remove(_object)

    def remove_action(self, _action):
        self.contained_actions.remove(_action)

    def add_objects(self, _objects):
        for _object in _objects:
            self.contained_objects.append(_object)

    def clear_objects(self):
        self.contained_objects = []

    def get_objects(self):
        return self.contained_objects

    def get_object_names(self):
        names = []
        for _object in self.contained_objects:
            names.append(_object.get_text_string())
        return names

    def get_action_names(self):
        names = []
        for _action in self.contained_actions:
            names.append(_action.get_text_string())
        return names

    def find_object(self, _name):
        for _object in self.contained_objects:
            if _object.get_text_string().strip() == _name:
                return _object

    def find_action(self, _name):
        for _action in self.contained_actions:
            if _action.get_text_string().strip() == _name:
                return _action

    def add_attribute(self, key, value):
        self.attributes[key] = value

    def remove_attribute(self, key):
        self.attributes.pop(key)

    def get_attribute(self, key):
        return self.attributes[key]

    def get_attributes(self):
        return self.attributes


# Save Classes

class GameSave(object):
    """Stores all game values"""

    def __init__(self, name="", add_event=None):
        self.name = name
        self.cryptic = False
        self.temp_cryptic = False
        self.camo = False
        self.camo_plus = False
        self.orpheus = False
        self.shield = False
        self.wolf = False
        self.fleece = False
        self.sack = False
        self.sleep = 0
        self.dead = False
        self.path = [
            GameObject([Text("Game", color=input_color)],
                       [
                           GameObject(
                               [Text("Inventory", color=inventory_color)]),
                           GameObject(
                               [Text("Surroundings", color=inventory_color)]),
                           GameObject(
                               [Text("Stats", color=inventory_color)],
                               attributes={
                                   "Character": NPC("yourself"),
                               },
                           ),
                           GameObject(
                               [Text("Attacks", color=inventory_color)],
                           ),
                       ]
                       )
        ]
        if isinstance(add_event, type(None)):
            raise Exception("Game Save object needs add_event")
        else:
            self.add_event = add_event
        self.path[0].find_object("Stats").add_object(
            GameObject([self.get_you_word(tooltip=[Text("")], strip=True)],
                       _actions=[GameAction([Text("Use")])]))
        self.update_weapons()
        self.update_attacks(True)
        # Equip first attack by default
        self.equip_default()
        self.current_objects = None  # For showing/hiding objects
        self.update_stats_tooltip()
        self.new_location = True
        self.name_nums = {}
        self.type_nums = {}
        self.uniques = []

        self.boosts = []
        self.accurate = 0
        self.viewed_npc = None
        self.herot = False

        self.hotkey_action = None
        self.current_path = None

    def notify_switch(self):
        self.add_event([Text("Equipped attack not available, switched to ",
                             color=prompt_color), Text(
            game_save.get_equipped().get_attribute("Attack").name + ".",
            tooltip=game_save.get_equipped().get_attribute("Attack").gen_desc(),
            new_line=True)])

    def update_attacks(self, first=False):
        """Clears actions and adds starting actions"""
        self.path[0].find_object("Attacks").clear_objects()
        current_attacks = []
        for attack in self.get_character().get_attacks():
            if attack.get_name() not in current_attacks:
                if attack.name not in current_attacks:
                    if attack.current_cooldown > 0:
                        attack_color = cooldown_color
                    elif attack.current_cooldown < 0:
                        attack_color = multiattack_color
                    elif "Self" in attack.attributes:
                        attack_color = self_color
                    else:
                        attack_color = default_color
                    self.path[0].find_object("Attacks").add_object(GameObject(
                        [Text(attack.get_name(), tooltip=attack.gen_desc(
                            None, False), color=attack_color)],
                        _actions=[GameAction("Equip")],
                        attributes={"Attack": attack}))
                    current_attacks.append(attack.get_name())

        for weapon in self.path[0].find_object("Inventory").get_objects():
            for attack in weapon.get_attribute("Item").get_attacks():
                if "Cryptic" in attack.get_attributes() and not self.cryptic:
                    continue
                attack_name = weapon.get_attribute(
                    "Item").get_name() + ":" + attack.get_name()
                if attack_name not in current_attacks:
                    if attack.current_cooldown > 0:
                        attack_color = cooldown_color
                    elif attack.current_cooldown < 0:
                        attack_color = multiattack_color
                    elif "Self" in attack.attributes:
                        attack_color = self_color
                    else:
                        attack_color = default_color

                    flower = False
                    if "Flower" in weapon.get_attribute("Item"). \
                            get_attributes() and not self.cryptic:
                        flower = True

                    self.path[0].find_object("Attacks").add_object(GameObject(
                        [Text(attack_name, tooltip=attack.gen_desc(
                            weapon.get_attribute("Item"), False, flower=flower),
                              color=attack_color)],
                        _actions=[GameAction("Equip")],
                        attributes={"Attack": attack, "Weapon": weapon}))
                    current_attacks.append(attack_name)

        inv = list([weap.get_attribute("Item") for weap in
                    self.get_path()[0].find_object(
                        "Inventory").get_objects()])
        equipped = True
        if "Equipped" not in self.path[0].find_object(
                "Attacks").get_attributes():
            equipped = False
        found = True
        if equipped and "Weapon" in self.get_equipped().get_attributes():
            weapon = self.get_equipped().get_attribute("Weapon").get_attribute(
                "Item")
            attack = self.get_equipped().get_attribute("Attack")
            if weapon not in inv:  # If weapon is lost, set all identical weapons to same cooldown for continued multiuse
                found = False
                for weap in inv:  # Update all cooldowns of same weapons/attacks
                    if weap.get_name() == weapon.get_name():
                        found = True
                        for atta in weap.get_attacks():
                            if atta.get_name() == attack.get_name():
                                atta.current_cooldown = attack.current_cooldown
                        weapon = weap
                if found:
                    for atta_obj in self.get_path()[0].find_object(
                            "Attacks").get_objects():  # Find same weapon/attack to equip instead
                        temp_atta = atta_obj.get_attribute("Attack")
                        if temp_atta.get_name() == attack.get_name():
                            if "Weapon" in atta_obj.get_attributes():
                                if atta_obj.get_attribute(
                                        "Weapon").get_attribute(
                                    "Item").get_name() == weapon.get_name():
                                    # Same attack name, same weapon name
                                    if temp_atta != attack:
                                        # Different attack
                                        self.path[0].find_object(
                                            "Attacks").add_attribute("Equipped",
                                                                     atta_obj)
                                        self.update_attacks_tooltip()
                                        break

        if not equipped or not found:

            self.equip_default()
            if not first:
                self.open_path()
                self.notify_switch()
                return True
        return False

    def open_path(self, pth="Attacks"):
        # pth = cap_all_words(pth, [">", "-", ":"])
        if pth == "Game":
            self.set_path_index(0)
        elif pth == "Back":
            if len(self.get_path()) > 1:
                self.remove_path_index(-1)
        else:
            self.set_path_index(0)
            for step in pth.split(">"):
                if step in self.get_path()[-1].get_object_names():
                    self.append_path(self.get_path()[-1].find_object(step))
                elif step in self.get_path()[-1].get_action_names():
                    return self.get_path()[-1].find_action(step)

    def update_attacks_tooltip(self):
        text_list = [Text("Equipped Attack", color=header_color,
                          font_size=header_size, new_line=True),
                     Text("", new_line=True)]
        attack = self.get_equipped().get_attribute("Attack")
        if "Weapon" in self.get_equipped().get_attributes():
            weapon = self.get_equipped().get_attribute("Weapon").get_attribute(
                "Item")
        else:
            weapon = None

        flower = False
        if not isinstance(weapon, type(None)):
            if "Flower" in weapon.get_attributes() and not self.cryptic:
                flower = True

        text_list += attack.gen_desc(weapon, flower=flower)
        self.get_path()[0].find_object("Attacks").set_tooltip(text_list)

    def rename_flowers(self):
        flowers = []

        for item in self.get_path()[0].find_object("Inventory").get_objects():
            flowers.append(item)

        for item in self.get_path()[0].find_object(
                "Surroundings").get_objects():
            flowers.append(item)

        for item in flowers:
            if "Item" in item.get_attributes():
                flower_item = item.get_attribute("Item")
                if "Flower" in flower_item.get_attributes():
                    if not self.cryptic:
                        new_name = flower_item.get_attributes()["Flower"]
                        if new_name != "Herb" and new_name != "Shirt-of-Nessus":
                            new_name += "-Flower"
                        item.hide_attacks_desc()
                    else:
                        new_name = flower_item.get_type()
                        item.show_attacks_desc()
                    flower_item.set_name(new_name)
                    item.set_text(new_name)

    def equip_default(self):
        self.path[0].find_object("Attacks").add_attribute(
            "Equipped", self.path[0].find_object(
                "Attacks").get_objects()[0])
        self.update_attacks_tooltip()

    def get_equipped(self):
        return self.get_path()[0].find_object("Attacks").get_attribute(
            "Equipped")

    def update_weapons(self):
        """Clears the inventory and adds starting weapons"""
        self.path[0].find_object("Inventory").clear_objects()
        for weapon in self.get_character().get_weapons():
            if "Treasure" in weapon.get_attributes():
                color = treasure_color
            else:
                color = weapon_color
            self.path[0].find_object("Inventory").add_object(GameObject(
                [Text(weapon.get_name(), color=color,
                      tooltip=weapon.gen_desc())], attributes={
                    "Item": weapon,
                }, _actions=[GameAction("Drop")]))

    def get_character(self):
        return self.path[0].find_object("Stats").get_attribute(
            "Character")

    def append_path(self, _object):
        self.path.append(_object)

    def remove_path_index(self, ind):
        self.path.pop(ind)

    def add_health_boost(self, healths, turns, name_word):
        """Additive effect"""
        self.boosts.append(
            {"Healths": healths, "Turns": turns, "Teller": name_word})
        self.boost_health(len(self.boosts) - 1, True)

    def add_attack_boost(self, attacks, turns, name_word):
        """Multiplicative effect"""
        self.boosts.append(
            {"Attacks": attacks, "Turns": turns, "Teller": name_word})

    def boost_health(self, ind, on=True):
        current_boost = self.boosts[ind]
        health = self.get_health().copy()
        for stat in current_boost["Healths"]:
            if stat not in health:
                health[stat] = 0
            if on:
                health[stat] += current_boost["Healths"][stat]
            else:
                health[stat] -= (current_boost["Healths"][stat] - 1)
        self.set_health(health)

    def boost_attacks(self):
        attacks_dict = {}
        for attacks in list(filter(lambda x: "Attacks" in x, self.boosts)):
            for attack in attacks["Attacks"]:
                if attack not in attacks_dict:
                    attacks_dict[attack] = 0
                attacks_dict[attack] += attacks["Attacks"][attack]
        return attacks_dict

    def check_boosts(self):
        if self.accurate > 0:
            self.accurate -= 1
            if self.accurate == 0:
                self.add_event(
                    [Text("The effects of hearing ", color=prompt_color),
                     self.accurate_word,
                     Text("'s story have worn off.", color=prompt_color,
                          new_line=True)])
        ind = 0
        for boost in self.boosts:
            boost["Turns"] -= 1
            if boost["Turns"] <= 0:
                if "Healths" in boost:
                    self.boost_health(ind, False)
                self.add_event(
                    [Text("The effects of hearing ", color=prompt_color),
                     boost["Teller"],
                     Text("'s story have worn off.", color=prompt_color,
                          new_line=True)])
                self.boosts.pop(ind)
            ind += 1

    def canterbury(self, npc, treasures):
        name_parts = npc.get_name().split("-")
        if name_parts[-1].isnumeric():
            name_parts.pop()
        name = reduce(lambda x, y: x + "-" + y, name_parts)

        if not name == "The-Franklin":
            for item in treasures:
                npc.weapons.append(item.get_attribute("Item"))

        desc = None

        if name == "The-Knight":
            desc = "inspires you to persevere through pain."
            self.add_health_boost({"Sharp": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Squire":
            desc = "inspires you to stay strong."
            self.add_health_boost({"Brute": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Plowman":
            desc = "inspires you to be more righteous."
            self.add_health_boost({"Divine": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Reeve":
            desc = "inspires you to be quick on your feet."
            self.add_health_boost({"Agility": 100000}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Franklin":
            desc = "inspires you to be friendlier."
            self.add_health_boost({"Charisma": 1}, 30,
                                  npc.get_word(True, True))
            self.add_event([npc.get_word(True, False),
                            Text("gave you back your payment.",
                                 color=prompt_color, new_line=True)])
            for _treasure in treasures:
                self.add_inv_item(_treasure)
        elif name == "The-Sailor":
            desc = "inspires you to be one with nature."
            self.add_attack_boost({"Tame": 2}, 30,
                                  npc.get_word(True, True))
            bestiary_word = Text(bestiary.get_name(), color=weapon_color,
                                 tooltip=bestiary.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 bestiary_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([bestiary_word],
                                         attributes={"Item": bestiary}))
        elif name == "The-Parson":
            desc = "inspires you to spread the word of God."
            self.add_attack_boost({"Divine": 2}, 30,
                                  npc.get_word(True, True))
        elif name == "The-Clerk":
            anthology_word = Text(anthology.get_name(), color=weapon_color,
                                  tooltip=anthology.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 anthology_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([anthology_word],
                                         attributes={"Item": anthology}))
        elif name == "The-Cook":
            cook_word = Text(cook_book.get_name(), color=weapon_color,
                             tooltip=cook_book.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 cook_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([cook_word],
                                         attributes={"Item": cook_book}))
        elif name == "The-Wife-of-Bath":
            atlas_word = Text(atlas.get_name(), color=weapon_color,
                              tooltip=atlas.gen_desc())
            self.add_event(
                [npc.get_word(), Text("handed you a ", color=prompt_color),
                 atlas_word, Text(".", color=prompt_color, new_line=True)])
            self.add_inv_item(GameObject([atlas_word],
                                         attributes={"Item": atlas}))
        elif name == "The-Yeoman":
            desc = "inspires you to focus in combat."
            self.accurate = 30
            self.accurate_word = npc.get_word(True, True)
        elif name == "The-Monk":
            game_save.gen_npc(monks_dogs)
            game_save.gen_npc(monks_dogs)
            self.add_event(
                [npc.get_word(True, True),
                 Text("'s dogs attack you while he tells you his story.",
                      color=prompt_color, new_line=True)])
        elif name == "The-Friar":
            health = self.get_health()
            health["Divine"] = 1
            self.set_health(health)
            self.add_event([npc.get_word(True),
                            Text("absolves your sins.", color=prompt_color,
                                 new_line=True)])
        elif name == "The-Merchant":
            success, items = self.get_payment(1, True)
            for item in items:
                npc.weapons.append(item.get_attribute("Item"))
        elif name == "The-Lawyer":
            success, items = self.get_payment(-1, True)
            for item in items:
                npc.weapons.append(item.get_attribute("Item"))

        if not isinstance(desc, type(None)):
            self.add_event([npc.get_word(True, True), Text("'s story ",
                                                           color=prompt_color),
                            Text(desc, color=prompt_color, new_line=True)])
        self.update_surroundings()

    def get_payment(self, num, trick=False):
        treasures = []
        treasure_words = []
        for _object in self.get_path()[0].find_object(
                "Inventory").get_objects():
            item = _object.get_attribute("Item")
            if "Treasure" in item.get_attributes():
                treasures.append(_object)
                treasure_words.append(
                    Text(item.get_name(), color=treasure_color,
                         tooltip=item.gen_desc()))
                if len(treasures) == num:
                    break
        if len(treasures) == num or num == -1:
            treasure_string = []
            i = 0
            for word in treasure_words:
                if i == 0:
                    treasure_string.append(word)
                elif i == len(treasure_words) - 1:
                    treasure_string.append(
                        Text(", and ", color=prompt_color))
                    treasure_string.append(word)
                else:
                    treasure_string.append(
                        Text(", ", color=prompt_color))
                    treasure_string.append(word)
                i += 1

            if trick:
                sentence = "He takes your "
            else:
                sentence = "You hand over "
            self.add_event(
                [Text(sentence, color=prompt_color),
                 *treasure_string,
                 Text(".", color=prompt_color,
                      new_line=True)])
            for item in treasures:
                self.remove_inv_item(item)

            return True, treasures
        else:
            if not trick:
                self.add_event([Text("You don't have enough treasure.",
                                     color=prompt_color, new_line=True)])
            return False, treasures

    def drop_empty(self, weapon_object, attack_object):
        self.add_event([Text("Equipped attack is out of ammo.", color=prompt_color,
                        new_line=True)])
        weapon_object = attack_object.get_attribute("Weapon")
        self.remove_inv_item(weapon_object)
        self.update_surroundings()

    def add_inv_item(self, item):
        self.path[0].find_object("Inventory").add_object(item)
        self.path[0].find_object("Inventory").get_objects()[-1].set_actions(
            [
                GameAction("Drop")])
        health = self.get_health()
        health["Agility"] -= 5
        self.set_health(health)  # Reduce agility

    def remove_inv_item(self, item):
        if item not in self.path[0].find_object("Inventory").get_objects():
            for _item in self.path[0].find_object(
                    "Inventory").get_objects():
                if item.get_text_string() == _item.get_text_string():
                    self.path[0].find_object("Inventory").remove_object(
                        _item)
                    break
        else:
            self.path[0].find_object("Inventory").remove_object(item)
        health = self.get_health()
        health["Agility"] += 5
        self.set_health(health)  # Increase agility

    def add_ground_item(self, item, npc=False):
        self.path[0].find_object("Surroundings").add_object(item)
        if not npc:
            self.path[0].find_object("Surroundings").get_objects()[
                -1].set_actions([GameAction("Pick-up")])

    def remove_ground_item(self, item):
        self.path[0].find_object("Surroundings").remove_object(item)

    def add_main_action(self, action):
        self.path[0].add_action(action)

    def clear_main_actions(self):
        self.path[0].clear_actions()

    def hide_objects(self):
        self.path[0].set_text_list([Text("Menu", color=input_color,
                                         font_size=path_font_size,
                                         font_name=path_font)])
        self.current_objects = self.path[-1].get_objects()[:]
        self.path[-1].clear_objects()

    def show_objects(self):
        self.path[0].set_text_list([Text("Game", color=input_color,
                                         font_size=path_font_size,
                                         font_name=path_font)])
        self.path[-1].add_objects(self.current_objects)
        self.current_objects = None

    def set_path_index(self, ind):
        self.path = self.path[:ind + 1]

    def get_path(self):
        return self.path

    def get_path_text_list(self, string=False):
        path_text_list = []
        for _object in self.get_path():
            if not string:
                path_obj = _object.get_text_object()
                path_obj.set_font(choice_font, choice_font_size)
                path_text_list.append(path_obj)
            else:
                path_text_list.append(_object.get_text_string())
            if type(_object) == GameObject:
                if not string:
                    path_text_list.append(Text(">", color=input_color,
                                               font_size=choice_font_size,
                                               font_name=choice_font))
                else:
                    path_text_list.append(">")
        if string:
            return "".join(path_text_list)
        return path_text_list

    def check_caught(self):
        """Check if cannot leave"""
        for npc in self.get_npcs():
            if npc.target == "You":
                if random.randint(0, npc.get_health()["Agility"]) > \
                        self.get_health()["Agility"]:
                    return npc  # You were caught
        return False

    def gen_location(self, location_name=None):
        """Chooses a random location to spawn in"""

        self.path[0].find_object("Surroundings").clear_objects()
        self.path[0].find_object("Surroundings").clear_actions()

        available_locations = list(filter(lambda
                                              x: x.get_name() != "Strange-Cavern" and x.get_name() != "Treasure-Hall",
                                          LOCATIONS_LIST))
        if "Grendel" in self.uniques:
            available_locations = list(
                filter(lambda x: x.get_name() != "Treasure-Hall",
                       LOCATIONS_LIST))
        if "Grendel's-Mother" in self.uniques:
            available_locations = LOCATIONS_LIST

        probabilities = [l.probability for l in available_locations]
        if isinstance(location_name, type(None)):
            location = weighted_choice(available_locations, probabilities)
        else:  # Given name, find location
            location = None
            for _location in available_locations:
                if _location.get_type() == location_name:
                    location = _location
                    break
        location.gen_name()
        self.path[0].find_object("Surroundings").add_attribute(
            "location", location)

        self.path[0].find_object("Surroundings").add_action(GameAction(
            "Leave-Location"))
        self.path[0].find_object("Surroundings").add_action(GameAction(
            "Wait"))
        self.path[0].find_object("Surroundings").set_tooltip(
            [Text("Location", color=header_color, bold=True, new_line=True,
                  font_size=header_size),
             Text(location.get_name(), new_line=True),
             Text("", new_line=True),
             *location.gen_desc()])
        # Gen items
        objects = []
        for _weapon in location.gen_objects(location.weapons):
            objects.append(GameObject([Text(_weapon.get_name(),
                                            color=weapon_color,
                                            tooltip=_weapon.gen_desc())],
                                      _actions=[GameAction("Pick-up")],
                                      attributes={"Item": _weapon}))

        tmax = location.treasure["Max"]
        tmin = location.treasure["Min"]
        tprob = location.treasure["Probability"]
        treasures = []
        if random.randint(0, 100) <= tprob:
            for i in range(0, random.randint(tmin, tmax)):
                treasure_item = treasure.copy(False)
                treasures.append(GameObject([Text(
                    treasure_item.get_name(), color=treasure_color,
                    tooltip=treasure_item.gen_desc())], _actions=[
                    GameAction("Pick-up")], attributes={"Treasure": True,
                                                        "Item": treasure_item}))

        self.gen_npcs()
        if location.get_name() == "Herot":
            if self.herot:
                self.gen_npc(grendel)
        self.path[0].find_object("Surroundings").add_objects(objects)
        self.path[0].find_object("Surroundings").add_objects(treasures)
        self.new_location = True
        self.rename_flowers()
        self.reset_cooldowns()

    def gen_npcs(self, wander=False):
        if not wander:
            self.name_nums = {}
            self.type_nums = {}
        # Gen NPCs
        location = self.get_path()[0].find_object(
            "Surroundings").get_attribute("location")
        if "Boss" not in location.get_attributes():
            npc_word_list = []
            npc_amounts = {}
            for npc in location.gen_objects(location.npcs, wander, self.type_nums):
                if self.gen_npc(npc):
                    npc = self.get_path()[0].find_object(
                        "Surroundings").get_objects()[-1].get_attribute(
                        "NPC")
                    npc_word_list.append(npc.get_word())
            return npc_word_list
        elif not wander:
            # Only spawn one random creature
            npcs = location.gen_objects(location.npcs)
            living_npcs = []
            for npc in npcs:
                if "Unique" in npc.get_attributes():
                    if npc.get_type() in self.uniques:
                        # Already been killed
                        continue
                living_npcs.append(npc)
            while len(living_npcs) > 0:
                if self.gen_npc(random.choice(living_npcs)):
                    npc = self.get_path()[0].find_object(
                        "Surroundings").get_objects()[-1].get_attribute(
                        "NPC")
                    return [npc.get_word()]
        return []

    def gen_npc(self, _npc, new_name=True):
        """Creates an npc and adds to surroundings"""
        npc = _npc.copy()
        if npc.get_type() in self.uniques and new_name:
            return False
        if new_name:
            npc.gen_name(self.name_nums, self.type_nums)
        else:
            npc.determine_number(self.name_nums, self.type_nums)

        weapon_objects = []
        for _weapon in npc.get_weapons():
            if "Treasure" in _weapon.get_attributes():
                color = treasure_color
            else:
                color = weapon_color
            weapon_objects.append(GameObject([
                Text(_weapon.get_name(), color=color,
                     tooltip=_weapon.gen_desc())],
                attributes={"Item": _weapon}))

        actions = [GameAction("Attack")]
        if "Story-Teller" in npc.get_attributes():
            if isinstance(npc.target, type(None)):
                actions.append(GameAction([Text("Pay", color=treasure_color,
                                                new_line=True)]))
        if "Hrothgar" in npc.get_attributes() and isinstance(npc.target,
                                                             type(None)):
            actions.append(GameAction([Text("Talk", new_line=True)]))

        self.add_ground_item(
            GameObject([npc.get_word(False)], _actions=actions,
                       _objects=weapon_objects,
                       attributes={"NPC": npc}), npc=True)
        return True

    def get_npcs(self):
        """Returns NPCs in current surroundings"""
        npcs = []
        for _object in self.path[0].find_object(
                "Surroundings").get_objects():
            if "NPC" in _object.get_attributes():
                npcs.append(_object.get_attribute("NPC"))

        return npcs

    def get_health(self):
        """Return Health Stats of self"""
        return self.get_character().get_health()

    def set_health(self, stats):
        """Sets the health stats for self"""
        self.get_character().set_health(stats)
        self.update_stats_tooltip()

    def update_stats_tooltip(self):
        """Updates stats tooltip with current info"""
        desc = [Text("Health", color=header_color, bold=True,
                     new_line=True, font_size=header_size)]
        for stat in self.get_health():
            desc += [Text(stat + ": ", color=header_color),
                     Text(str(self.get_health()[stat]), new_line=True,
                          color=number_color)]
        self.path[0].find_object("Stats").set_tooltip(desc)
        self.path[0].find_object("Stats").find_object("You").set_tooltip(
            desc)

    def get_you_word(self, caps=True, tooltip=None, strip=False,
                     yourself=False):
        if caps:
            word = "You "
        else:
            word = "you "
        if yourself:
            word = "yourself"
        if strip:
            word = word.strip()
        if isinstance(tooltip, type(None)):
            tooltip = self.get_stats_tooltip()[:]
        return Text(word, tooltip=tooltip,
                    color=self_color)

    def get_stats_tooltip(self):
        return self.path[0].find_object("Stats").get_tooltip()

    def update_surroundings(self):
        self.viewed_npc = None
        if "NPC" in self.get_path()[-1].get_attributes():
            self.viewed_npc = self.get_path()[-1].get_attribute(
                "NPC").get_name()
        in_surroundings = False
        if self.path[-1] == self.path[0].find_object("Surroundings"):
            self.remove_path_index(-1)
            in_surroundings = True
        new_objects = []
        new_npcs = []
        for _object in self.path[0].find_object(
                "Surroundings").get_objects():
            if "NPC" not in _object.get_attributes():
                new_objects.append(_object)
            else:
                new_npcs.append(_object.get_attribute("NPC"))
        self.path[0].find_object("Surroundings").clear_objects()
        for npc in new_npcs:
            self.gen_npc(npc, new_name=False)
        self.path[0].find_object("Surroundings").add_objects(new_objects)
        if in_surroundings:
            self.open_path("Surroundings")
        if not isinstance(self.viewed_npc, type(None)):
            self.remove_path_index(-1)
            self.append_path(
                self.get_path()[0].find_object(
                    "Surroundings").find_object(self.viewed_npc))

    def reset_cooldowns(self):
        for attack in self.get_path()[0].find_object(
                "Attacks").get_objects():
            attack_item = attack.get_attribute("Attack")
            attack_item.reset_cooldown()

    def tick(self):
        for attack in self.get_path()[0].find_object(
                "Attacks").get_objects():
            attack_item = attack.get_attribute("Attack")
            if attack_item.tick():
                weapon = None
                flower = False
                if "Weapon" in attack.get_attributes():
                    weapon = attack.get_attribute("Weapon").get_attribute(
                        "Item")
                    if "Flower" in weapon.get_attributes() and not self.cryptic:
                        flower = True
                self.add_event([Text(attack.get_text_string() + " ",
                                     tooltip=attack_item.gen_desc(weapon,
                                                                  False,
                                                                  None,
                                                                  flower)),
                                Text("is ready to use.", color=prompt_color,
                                     new_line=True)])

    def check_combat(self):
        current_living_npcs = self.get_npcs()[:]
        while len(current_living_npcs) > 0:
            _npc = current_living_npcs.pop(0)
            if "Chained" in _npc.attributes:
                continue
            attack = None
            weapon = None
            while True:
                if not isinstance(weapon,
                                  type(None)) and weapon not in _npc.weapons:
                    found = False
                    for weap in _npc.weapons:
                        if weap.get_name() == weapon.get_name():
                            for atta in weap.get_attacks():
                                if atta.get_name() == attack.get_name():
                                    atta.current_cooldown = attack.current_cooldown
                                    break
                            weapon = weap
                            found = True
                            break  # Found same type of weapon, allow continued attack
                    if not found:
                        weapon = None
                        break  # End multiattack turn because weapon was lost
                targets = []
                npc_teams = []
                if "Team" in _npc.get_attributes():
                    npc_teams = _npc.get_attribute("Team")
                for _target in self.get_npcs():
                    if _target != _npc:
                        target_teams = []
                        if "Team" in _target.get_attributes():
                            target_teams = _target.get_attribute("Team")
                        if "All" in target_teams:
                            # Target is a friend to all
                            continue
                        elif len(list(set(target_teams) & set(
                                npc_teams))) > 0:
                            # Shares a team
                            continue
                        elif "All" in npc_teams and not ("You" in target_teams):
                            continue  # Do not add enemy to teams unless attacking a teammate of player's
                        if _target.get_type() == _npc.get_type():
                            if "Cannibal" in _npc.get_attributes():
                                targets.append(_target)
                        else:
                            targets.append(_target)

                your_team = "You" in npc_teams
                if not self.check_if_dead() and (not your_team or _npc.target == "You"):
                    targets.append("You")
                target = _npc.get_target(targets, self.camo, self.camo_plus)

                # Find target based on chosen target name
                for _target in targets:
                    if (_target != "You" and _target.get_name() == target) \
                            or target == _target:
                        target = _target
                        break
                if not isinstance(target, type(None)):
                    if target == "You":
                        health = self.get_health()
                    else:
                        health = target.get_health()
                    stats, damages, attack, weapon, multiattack = _npc.attack(
                        health, attack, weapon)
                    if target != "You":
                        target.target = _npc.get_name()
                    if not isinstance(attack, type(None)):
                        if "Thrown" in attack.get_attributes():
                            self.path[0].find_object(
                                "Surroundings").add_object(
                                GameObject([Text(weapon.get_name(),
                                                 color=weapon_color,
                                                 tooltip=weapon.gen_desc())],
                                           _actions=[
                                               GameAction("Pick-up")],
                                           attributes={"Item": weapon}))
                            _npc.weapons.remove(weapon)
                        elif "Consumable" in attack.get_attributes():
                            _npc.weapons.remove(weapon)
                        elif "Disarm" in attack.get_attributes():
                            if target == "You":
                                if "Weapon" in self.get_equipped(). \
                                        get_attributes():
                                    item = self.get_equipped().get_attribute(
                                        "Weapon")
                                    self.add_ground_item(
                                        item)  # Add to ground
                                    self.remove_inv_item(
                                        item)  # Remove from inventory
                                    item = item.get_attribute("Item")
                                    self.add_event([self.get_you_word(),
                                                    Text("dropped "),
                                                    Text(item.get_name(),
                                                         color=weapon_color,
                                                         tooltip=item.gen_desc(),
                                                         new_line=True)])
                            else:
                                if len(target.weapons) > 0:
                                    # Remove random weapon
                                    disarmed_weapon = random.choice(
                                        _npc.weapons)
                                    _npc.weapons.remove(disarmed_weapon)
                                    self.get_path()[0].find_object(
                                        "Surroundings").add_object(
                                        GameObject(
                                            [Text(
                                                disarmed_weapon.get_name(),
                                                color=weapon_color, tooltip=
                                                disarmed_weapon.gen_desc())],
                                            attributes={
                                                "Item": disarmed_weapon},
                                            _actions=[
                                                GameAction("Pick-up")]))
                                    self.add_event(
                                        [_npc.get_word(), Text("dropped "),
                                         Text(
                                             disarmed_weapon.get_name(),
                                             color=weapon_color,
                                             tooltip=disarmed_weapon.gen_desc(),
                                             new_line=True)])
                        elif "Sleep+" in attack.get_attributes():
                            if target == "You":
                                if not game_save.orpheus:
                                    game_save.sleep = 10
                                    self.add_event(
                                        [Text("You have fallen into a deep sleep.",
                                              new_line=True, color=prompt_color)])
                                elif "Sleep+" in attack.get_attributes() and game_save.orpheus:
                                    game_save.orpheus = False
                                    self.add_event([Text(
                                        "The music of Orpheus\\'s Lyre keeps you alert. Its effects have now worn off.",
                                        new_line=True, color=prompt_color)])
                        elif "Wolf" in attack.get_attributes():
                            if target == "You":
                                game_save.wolf = True
                        if target == "You":
                            self.set_health(stats)
                        else:
                            target.set_health(stats)
                        is_dead = False
                        life = False
                        if len(damages) >= 1:
                            # Hit
                            tame = False
                            if target == "You":
                                is_dead = self.check_if_dead()
                            else:
                                is_dead, tame, life = target.check_if_dead()
                            self.hit_event(attack, damages, _npc, weapon,
                                           is_dead, tame, target)
                        else:
                            self.miss_event(attack, damages, _npc, weapon,
                                            target)

                        if target != "You" and is_dead:
                            # Target was killed
                            if "Unique" in target.get_attributes():
                                self.uniques.append(target.get_type())
                            if target in current_living_npcs:
                                current_living_npcs.remove(target)
                            npc_num = self.get_npcs().index(target)
                            self.get_path()[0].find_object(
                                "Surroundings").contained_objects.pop(
                                npc_num)
                            for _weapon in target.get_weapons():
                                if "Retained" not in _weapon.get_attributes() \
                                        and not life:
                                    self.get_path()[0].find_object(
                                        "Surroundings").add_object(
                                        GameObject(
                                            [Text(_weapon.get_name(),
                                                  color=weapon_color,
                                                  tooltip=
                                                  _weapon.gen_desc())],
                                            attributes={
                                                "Item": _weapon},
                                            _actions=[
                                                GameAction("Pick-up")]))
                            self.drop_ambrosia(target)
                            if game_save.viewed_npc == target.get_name():
                                # remove inv of observed npc
                                self.remove_path_index(-1)
                else:
                    break  # No targets
                if not multiattack:
                    _npc.tick()
                    break

        self.update_surroundings()

    def drop_ambrosia(self, target):
        if random.randint(1, 20000) \
                <= target.get_health()["Tame"]:
            self.add_event([Text(
                "Ambrosia dropped to the ground.",
                color=prompt_color, new_line=True)])
            self.get_path()[0].find_object(
                "Surroundings").add_object(GameObject(
                [Text(ambrosia.get_name(),
                      color=weapon_color,
                      tooltip=ambrosia.gen_desc())],
                attributes={
                    "Item": ambrosia,
                }, _actions=[GameAction("Pick-up")]))

    def check_egg(self):
        egg = False
        for weapon in self.get_path()[0].find_object(
                "Inventory").get_objects():
            if "Egg" in weapon.get_attribute("Item").attributes:
                self.remove_inv_item(weapon)
                self.set_health(starting_health)
                self.update_attacks()
                egg = True
        if egg:
            self.add_event([Text("YOU ARE DEAD", color=dead_color,
                                 font_name=dead_font,
                                 font_size=dead_font_size, new_line=True)])
            time.sleep(2)

            self.add_event([Text(
                "You hear a crack.",
                color=prompt_color, new_line=True)])

            time.sleep(2)

            self.add_event([Text("YOU LIVE AGAIN", color=dead_color,
                                 new_line=True, font_size=dead_font_size,
                                 font_name=dead_font)])
        return egg

    def check_if_dead(self):
        return self.get_character().check_if_dead()[0]

    def hit_event(self, _attack, damages, attacker, _weapon,
                  _is_dead=False, tame=False, target=None):

        is_you_target = target == "You" or target.get_name() == "yourself"
        is_you_attacker = attacker == "You" or attacker.get_name() == "yourself"

        if is_you_attacker:
            attacker_word = self.get_you_word()
        else:
            attacker_word = attacker.get_word()
        attack_word = Text(_attack.past_tense + " ",
                           tooltip=self.gen_attack_tooltip(_attack,
                                                           damages),
                           color=attack_color)
        if is_you_target:
            target_word = self.get_you_word(caps=False, strip=True,
                                            yourself=is_you_attacker)
        else:
            target_word = target.get_word(strip=True)
        text_list = [attacker_word, attack_word, target_word]

        flower = False
        if not isinstance(_weapon, type(None)):
            if "Flower" in _weapon.get_attributes() and not self.cryptic:
                flower = True

        if not isinstance(_weapon, type(None)):
            text_list += [Text(" "), Text("with "),
                          Text(_weapon.get_name().replace("-", " "),
                               color=weapon_color,
                               tooltip=_weapon.gen_desc(flower=flower))]
        if tame:
            text_list.append(Text(" "))
            text_list.append(Text("taming them"))
        elif _is_dead and not is_you_target:
            text_list.append(Text(" "))
            text_list.append(Text("killing them"))

        text_list.append(Text(".", new_line=True))
        self.add_event(text_list)

    def miss_event(self, _attack, damages, attacker, _weapon, target):
        is_you_target = target == "You" or target.get_name() == "yourself"
        is_you_attacker = attacker == "You" or attacker.get_name() == "yourself"

        if is_you_attacker:
            attacker_word = self.get_you_word()
        else:
            attacker_word = attacker.get_word()
        attack_word = Text(_attack.name.lower().replace("-", " ") + " ",
                           tooltip=self.gen_attack_tooltip(_attack,
                                                           damages),
                           color=attack_color)
        if is_you_target:
            target_word = self.get_you_word(caps=False, strip=True,
                                            yourself=is_you_attacker)
        else:
            target_word = target.get_word()
        text_list = [attacker_word, Text("tried to "), attack_word,
                     target_word]

        flower = False
        if not isinstance(_weapon, type(None)):
            if "Flower" in _weapon.get_attributes() and not self.cryptic:
                flower = True

        if not isinstance(_weapon, type(None)):
            text_list += [Text(" "), Text("with "),
                          Text(_weapon.get_name().replace("-", " "),
                               color=weapon_color,
                               tooltip=_weapon.gen_desc(flower=flower))]
        text_list.append(Text(" "))
        text_list.append(Text("but missed.", new_line=True))
        self.add_event(text_list)

    @staticmethod
    def gen_attack_tooltip(_attack, damages):
        # Add Attack name to attack tooltip
        desc = [Text(_attack.get_name(), font_size=header_size,
                     color=header_color, new_line=True),
                Text("Current Cooldown: "), Text(str(
                _attack.display_current_cooldown()), color=number_color,
                new_line=True)]
        if len(damages) == 0:
            desc.append(Text("No damage.", new_line=True))
        else:
            # Add attack stats to attack tooltip
            for damage in damages:
                desc += [Text(damage + ": ", color=header_color),
                         Text(str(damages[damage]), new_line=True,
                              color=number_color)]
        return desc

    game_save = None


def start(_get_input, update_textbox, _set_choices, mark_temporary,
          clear_temporary, is_running, set_caption, check_hotkeys,
          disable_hotkeys, enable_hotkeys, set_dead):
    """Function to start gameplay"""
    global game_save

    # TODO: Hotkey's/Shortcuts box?
    # TODO: Choose an object and see its actions

    # Interface functions

    def add_event(_event=""):
        """Adds an event to the events box

        :type _event: str | list[Text]
        :param _event: "" for new line
        """

        if _event == "":
            _event = [Text("", new_line=True)]

        update_textbox("events", _event)

    game_save = GameSave(add_event=add_event)

    def get_input(prompt=None, display_answer=True, allow_path_change=True,
                  constrict=True, hotkeys=False):
        """Creates a prompt and waits for input

        :type prompt: str | None
        :type display_answer: bool
        :type allow_path_change: bool
        :type constrict: bool
        :type hotkeys: bool
        """

        set_choices(constrict)
        if not hotkeys:
            disable_hotkeys()
        user_input, path_changed = _get_input(prompt, game_save.get_path(),
                                              allow_path_change)
        if not hotkeys:
            enable_hotkeys()
        if isinstance(path_changed, type(None)):
            # Main thread quit
            return None, None

        if display_answer:
            add_event([Text("Menu>", color=input_color,
                            font_size=path_font_size, font_name=path_font),
                       Text(user_input, color=input_color,
                            font_size=path_font_size, font_name=path_font)])

        return user_input, path_changed

    def find_choice(_choice):
        """Returns the object or action chosen based on given name

        :type _choice: str
        :rtype: GameObject | GameAction | None
        """

        if _choice == "":
            return

        # Down a path
        current_objects = game_save.get_path()[-1].get_objects()
        current_actions = game_save.get_path()[-1].get_actions()

        for _object in current_objects:
            if _object.get_text_string().strip() == _choice:
                return _object
        for _action in current_actions:
            if _action.get_text_string().strip() == _choice:
                return _action
                # raise Exception("Could not find choice: " + _choice)

    def set_choices(constrict=True):
        """Sets the choices to cycle between"""
        if constrict:
            _set_choices(game_save.get_path()[-1].get_objects(),
                         game_save.get_path()[-1].get_actions())
        else:
            _set_choices([], [])

    def get_from_choices(prompt, choices, display_answer=True):
        game_save.set_path_index(0)
        game_save.hide_objects()
        for choice in choices:
            game_save.add_main_action(GameAction([Text(choice)]))
        _choice = get_choice(prompt, display_answer=display_answer)
        if isinstance(_choice, type(None)):
            save_to_file(game_save, game_save.name + "." + extension)
            raise SystemExit

        game_save.clear_main_actions()
        game_save.show_objects()
        display_objects()
        return _choice

    def select_action(chosen_object, display_answer, return_string):
        if display_answer:
            # Get Path and reduce font size
            path_text_list = []
            for _object in game_save.get_path_text_list():
                object_text = _object.copy(False)
                object_text.set_font(path_font, path_font_size)
                path_text_list.append(object_text)

            # Get chosen object and reduce font size
            object_text = chosen_object.get_text_object().copy(
                False)
            object_text.set_font(path_font, path_font_size)
            path_text_list.append(object_text)

            # Add newline and add to events
            path_text_list[-1].new_line = True
            add_event(path_text_list)

        if return_string:
            return chosen_object.get_text_string().strip()
        return chosen_object

    def get_choice(prompt=None, display_answer=True, return_string=True,
                   allow_path_change=True, choose_action=False, hotkeys=False):
        """Gets user input based on current objects/actions

        :type prompt: None | str
        :type display_answer: bool
        :type return_string: bool
        :type allow_path_change: bool
        :type choose_action: bool
        """

        if not isinstance(prompt, type(None)):  # Display Prompt
            add_event([Text(prompt, color=prompt_color, new_line=True)])

        display_objects()
        chosen_object, back = get_input(display_answer=False,
                                        allow_path_change=allow_path_change,
                                        hotkeys=hotkeys)
        if isinstance(back, type(None)):
            # Main thread quit
            return None

        if back == len(game_save.get_path()) - 1:
            # Path was not changed

            chosen_object = find_choice(chosen_object)
            if type(chosen_object) == GameObject:
                game_save.append_path(chosen_object)
            if type(chosen_object) == GameAction or not choose_action:

                return select_action(chosen_object, display_answer,
                                     return_string)

            return get_choice(None, display_answer, return_string,
                              allow_path_change, choose_action, hotkeys)

        elif back == "HOTKEY":
            current_path = game_save.get_path_text_list(True)
            action = game_save.open_path(chosen_object)
            if not isinstance(action, type(None)):
                game_save.current_path = current_path
                return select_action(action, display_answer, return_string)
            display_objects()
            return get_choice(None, display_answer, return_string,
                              allow_path_change, choose_action, hotkeys)
        else:
            # Path moved back via delete
            if len(game_save.get_path()) > 1:
                game_save.set_path_index(back)
            display_objects()
            return get_choice(None, display_answer, return_string,
                              allow_path_change, choose_action, hotkeys)

    def get_yes(prompt=None, display_answer=True, choices=None):
        """Returns the users response to a yes/no question

        :type prompt: None | str
        :type display_answer: bool
        :type choices: None | list[str]
        :param choices: Provide two choices in a list. 'Yes'/'No' if None
        """
        if not isinstance(prompt, type(None)):
            add_event(
                [Text(prompt, None, color=prompt_color, new_line=True)])

        if isinstance(choices, type(None)):
            choices = ["Yes", "No"]

        if len(choices) > 2:
            raise Exception("Too many choices provided")

        game_save.set_path_index(0)
        game_save.hide_objects()
        game_save.add_main_action(GameAction([Text(choices[0])]))
        game_save.add_main_action(GameAction([Text(choices[1])]))

        _choice = get_choice(display_answer=display_answer)
        if isinstance(_choice, type(None)):
            raise SystemExit

        game_save.clear_main_actions()
        game_save.show_objects()
        display_objects()

        if _choice == choices[0]:
            return True
        if _choice == choices[1]:
            return False
        raise Exception("Non yes/no answer given to yes/no question")

    def continue_prompt():
        """Prompts the user to press 'Enter'"""
        add_event([Text("Press 'Enter' to continue...", new_line=True,
                        color=input_color)])

        game_save.set_path_index(0)

        game_save.hide_objects()

        display_objects()
        while True:
            user_input = get_input(display_answer=False,
                                   allow_path_change=False)
            if isinstance(user_input[1], type(None)):
                raise SystemExit
            elif user_input[1] != "HOTKEY":
                break

        game_save.show_objects()

        display_objects()

    def display_objects():
        """Sends the current objects to the gui"""

        set_choices()

        # Object selected
        current_objects = game_save.get_path()[-1].get_objects()
        current_actions = game_save.get_path()[-1].get_actions()

        if len(current_objects) < 1:  # Clear box if empty
            update_textbox("objects", "", True)
        if len(current_actions) < 1:
            update_textbox("actions", "", True)

        first = True  # Clear textbox on first word
        i = 1
        for _object in current_objects:
            comma = ""
            if i < len(current_objects):
                comma = ","
            text = _object.get_text_list()[:]
            text[-1].strip()
            text[-1].append(comma)
            text[-1].append(" ")
            update_textbox("objects", text, first)
            first = False
            i += 1

        first = True  # Clear textbox on first word
        i = 1
        for _action in current_actions:
            comma = ""
            if i < len(current_actions):
                comma = ","
            text_list = _action.get_text_list()
            text_list[-1].strip()
            text_list[-1].append(comma)
            text_list[-1].append(" ")
            update_textbox("actions", text_list, first)
            first = False
            i += 1

        # Display path

        update_textbox("path", game_save.get_path_text_list(), True)

    # Game Functions

    def tutorial():
        """Displays the tutorial"""
        # TODO: Update. Add Gameplay
        if get_yes("Do you want to view a tutorial? "
                   "(Use 'Tab' and 'Enter' to select an option)",
                   choices=["View", "Skip"]):

            prompt = "What would you like to learn about?"

            while True:

                _choice = get_from_choices(prompt, list(tutorials) + ["Cancel"])

                if _choice != "Cancel":
                    for line in tutorials[_choice]:
                        add_event([Text(line, new_line=True)])
                        continue_prompt()
                        add_event()
                    add_event()
                else:
                    break

    def save_and_load():
        """Save or load a game"""

        global game_save

        saves = list(filter(lambda x: x.endswith("." + extension),
                            get_files(
                                get_saves_path())))  # Get all save files
        if len(saves) < 1 or get_yes("Would you like to start a new game?",
                                     choices=["New", "Load"]):
            game_save.hide_objects()
            display_objects()

            name, _exit = get_input(
                "What do you want to name your game save?",
                constrict=False)
            if isinstance(_exit, type(None)):
                raise SystemExit

            game_save.show_objects()
            display_objects()

            game_save = GameSave(name, add_event=add_event)
            game_save.gen_location("Town")
            save_to_file(game_save, name + "." + extension)
        else:
            game_save.set_path_index(0)
            game_save.hide_objects()

            for file in saves:
                game_save.add_main_action(GameAction(
                    file[:-len(extension) - 1]))

            save_file = get_choice("Choose a save file.")
            if isinstance(save_file, type(None)):
                raise SystemExit
            save_file += "." + extension

            game_save.clear_main_actions()
            game_save.show_objects()

            game_save = load_from_file(save_file)
            game_save.set_path_index(0)
            game_save.add_event = add_event
            display_objects()
        set_caption("The Odyssey+: " + game_save.name)

    def display_location():
        location = game_save.get_path()[0].find_object(
            "Surroundings").get_attribute("location")
        add_event(
            [game_save.get_you_word(), Text("are in "),
             Text(location.get_name(), color=location_color,
                  tooltip=location.gen_desc()), Text(".", new_line=True)])

    # Start game

    tutorial()

    save_and_load()

    add_event()

    # Spawn Location
    if not game_save.dead:
        display_location()

        # Main game loop
        alive = not game_save.check_if_dead()
    else:
        alive = False
    while alive:  # Main game loop
        save_to_file(game_save, game_save.name + "." + extension)

        multiattack = False
        # Combat

        game_save.update_attacks()

        if game_save.check_if_dead():
            if not game_save.check_egg():
                break  # Died, break loop

        game_save.check_combat()

        game_save.check_boosts()

        if game_save.check_if_dead():
            if not game_save.check_egg():
                break  # Died, break loop

        game_save.update_attacks()

        choosing = True

        # Choices
        if game_save.sleep > 0:
            game_save.sleep -= 1
            if game_save.sleep == 0:
                add_event([Text("You awaken.", color=prompt_color,
                                new_line=True)])

        if game_save.temp_cryptic:
            if game_save.cryptic:
                game_save.temp_cryptic = False
                game_save.cryptic = False
            else:
                game_save.cryptic = True
            game_save.rename_flowers()

        weapon = None

        while (choosing or multiattack) and game_save.sleep == 0:  # Main choice loop
            cancel = False
            reset_cooldowns = False
            if not isinstance(game_save.current_path, type(None)):
                game_save.open_path(game_save.current_path)
                game_save.current_path = None
            choice = get_choice(return_string=True, choose_action=True,
                                hotkeys=True)
            if isinstance(choice, type(None)):
                # Game exit
                break

            if game_save.fleece:
                if game_save.get_health()["Brute"] < 10000:
                    game_save.get_health()["Brute"] += 200
                if game_save.get_health()["Sharp"] < 10000:
                    game_save.get_health()["Sharp"] += 200
                if game_save.get_health()["Burn"] < 10000:
                    game_save.get_health()["Burn"] += 200
                if game_save.get_health()["Freeze"] < 10000:
                    game_save.get_health()["Freeze"] += 200
                if game_save.get_health()["Divine"] < 10000:
                    game_save.get_health()["Divine"] += 300
                if game_save.get_health()["Agility"] < 1000:
                    game_save.get_health()["Agility"] += 50
                game_save.update_stats_tooltip()

            if choice == "Pick-up":
                if not multiattack:
                    multiattack = False
                    item = game_save.get_path()[-1]

                    too_heavy = False
                    if "Heavy" in item.get_attribute(
                                    "Item").get_attributes():
                        too_heavy = game_save.get_health()["Brute"] < 10000
                    if not too_heavy:
                        if game_save.get_path()[0].find_object(
                                "Surroundings").get_attribute(
                                "location").get_type() != "Town":
                            success = True
                        else:
                            success, treasures = game_save.get_payment(1, False)
                        if success:
                            if "Spawn-Cerastes" in item.get_attribute(
                                    "Item").get_attributes():
                                game_save.gen_npc(cerastes)
                                add_event([Text("You pull a ", color=dead_color),
                                           cerastes.get_word(),
                                           Text("out of the sand.",
                                                color=dead_color,
                                                new_line=True)])
                            elif "Smite" in item.get_attribute(
                                    "Item").get_attributes():
                                add_event([Text(
                                    "Zeus smote you with a lightning bolt.",
                                    color=dead_color, new_line=True)])
                                game_save.get_health()["Divine"] = 0
                                if not game_save.check_egg():
                                    break  # Died, break loop
                            elif "Sack" in item.get_attribute("Item").get_attributes():
                                game_save.sack = True
                            elif "Head" in item.get_attribute("Item").get_attributes():
                                if not game_save.sack:
                                    add_event([Text(
                                        "Medusa\'s eyes stare into your soul.",
                                        color=dead_color, new_line=True)])
                                    game_save.get_health()["Soul"] = 0
                                    if not game_save.check_egg():
                                        break  # Died, break loop
                                else:
                                    add_event([Text(
                                        "You pick up the head with the Kibisis.",
                                        color=prompt_color, new_line=True)])
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                            elif "Shield" in item.get_attribute("Item").get_attributes():
                                if not game_save.shield:
                                    game_save.shield = True
                                    game_save.get_health()["Brute"] += 100000
                                    game_save.get_health()["Sharp"] += 100000
                                    game_save.get_health()["Burn"] += 100000
                                    game_save.get_health()["Agility"] -= 100
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                                    game_save.update_stats_tooltip()
                                else:
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                            elif "Fleece" in item.get_attribute("Item").get_attributes():
                                if not game_save.fleece:
                                    game_save.fleece = True
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                                else:
                                    item.get_attribute("Item").reset_cooldowns()
                                    game_save.add_inv_item(item)  # Add to inventory
                            else:
                                item.get_attribute("Item").reset_cooldowns()
                                game_save.add_inv_item(item)  # Add to inventory
                            game_save.remove_ground_item(item)  # Remove from ground
                            game_save.remove_path_index(-1)  # Remove from Path
                            choosing = False
                    else:
                        add_event([Text("The item is too heavy.",
                                        color=prompt_color, new_line=True)])
                else:
                    add_event([Text("You have not finished your attack.",
                                    color=prompt_color, new_line=True)])

            elif choice == "Drop":
                multiattack = False
                item = game_save.get_path()[-1]
                item.get_attribute("Item").reset_cooldowns()
                if "Fleece" in item.get_attribute("Item").get_attributes():
                    game_save.fleece = False
                game_save.add_ground_item(item)  # Add to ground
                game_save.remove_inv_item(item)  # Remove from inventory
                game_save.remove_path_index(-1)  # Remove from Path

            elif choice == "Leave-Location":
                multiattack = False
                monster = game_save.check_caught()

                location = game_save.get_path()[0].find_object("Surroundings").\
                    get_attribute("location")

                if monster:  # Objects eval to True
                    # Was caught
                    add_event([monster.get_word(), Text("caught "),
                               game_save.get_you_word(False, strip=True),
                               Text(".", new_line=True)])
                    choosing = False
                else:
                    # Escaped
                    if location.get_name() == "Labyrinth":
                        if random.randint(0, 20) == 20:
                            game_save.gen_location()
                            add_event([Text(
                                "You escaped the Labyrinth.",
                                color=prompt_color, new_line=True)])
                        else:
                            game_save.gen_location("Labyrinth")
                    else:
                        game_save.gen_location()
                    display_location()

                if "Jumpscare" in location.get_attributes():
                    choosing = False

            elif choice == "Wait":
                multiattack = False
                choosing = False

            elif choice == "Equip":
                equipped_attack = game_save.get_path()[0].find_object(
                    "Attacks").get_attribute("Equipped").get_attribute(
                    "Attack")
                if equipped_attack.current_cooldown <= 0 or "Cooldown" \
                        in equipped_attack.get_attributes():
                    game_save.get_path()[0].find_object("Attacks"). \
                        add_attribute("Equipped", game_save.get_path()[-1])
                    game_save.update_attacks_tooltip()
                    if "Self" in game_save.get_path()[-1].get_attribute(
                            "Attack").get_attributes():
                        game_save.set_path_index(0)
                        stats = game_save.get_path()[0].find_object("Stats")
                        game_save.get_path().append(stats)
                        game_save.get_path().append(
                            stats.find_object("You"))
                    else:
                        game_save.set_path_index(0)
                        game_save.append_path(
                            game_save.get_path()[0].find_object(
                                "Surroundings"))
                    if multiattack:  # If multiattacking, end turn
                        multiattack = False
                        choosing = False
                else:
                    add_event(
                        [Text("Cannot change weapons while equipped is "
                              "cooling down.", color=prompt_color,
                              new_line=True)])
            elif choice == "Talk":
                npc = game_save.get_path()[-1].get_attribute(
                    "NPC")
                if not multiattack:
                    item = GameObject(
                        [Text(hrunting.get_name(),
                              color=weapon_color,
                              tooltip=hrunting.gen_desc())],
                        attributes={"Item": hrunting,
                                    }, _actions=[GameAction("Drop")])
                    game_save.add_inv_item(item)
                    add_event([Text("Hrothgar gives you Hrunting on behalf of Unferth.",
                                    color=prompt_color, new_line=True)])
                    add_event([npc.get_word(), Text(
                        "says, 'The holy Creator usward sent him, To West-Dane warriors, I ween, for to render ’Gainst Grendel’s grimness gracious assistance: I shall give to the good one gift-gems for courage.'",
                        color=prompt_color, new_line=True)])
                    game_save.herot = True
                    game_save.uniques.append(npc.get_type())
                    game_save.get_path()[-1].remove_action(
                        game_save.get_path()[-1].get_actions()[1])
                else:
                    add_event([Text("You have not finished your attack.",
                                    color=prompt_color, new_line=True)])
            elif choice == "Pay":
                npc = game_save.get_path()[-1].get_attribute(
                    "NPC")
                if not multiattack:
                    success, treasures = game_save.get_payment(1)
                    if success:
                        npc.paid = True
                        game_save.canterbury(
                            npc, treasures)
                        multiattack = False
                        choosing = False
                else:
                    add_event([Text("You have not finished your attack.",
                                    color=prompt_color, new_line=True)])

            elif choice == "Attack" or choice == "Use":
                disarmed_weapon = None
                power = False
                # Get equipped attack
                attack_object = game_save.get_path()[0].find_object(
                    "Attacks").get_attribute("Equipped")
                # Get Attack from object
                attack = attack_object.get_attribute("Attack")

                # Check if attack has cooldown
                if attack.ammo > 0 and attack.current_ammo < 1:
                    multiattack = False
                    game_save.drop_empty(weapon_object, attack_object)
                elif attack.current_cooldown > 0:
                    add_event(
                        [Text("Equipped attack is still cooling down.",
                              color=prompt_color, new_line=True)])
                    game_save.open_path("Surroundings")
                else:
                    # Get target npc
                    npc_num = 0
                    is_you = False
                    if game_save.get_path()[-1].get_text_string() == "You":
                        _npc = game_save.get_character()
                        is_you = True
                    else:
                        if "Self" in attack.get_attributes():
                            add_event(
                                [Text("Can only be used on self.",
                                      color=prompt_color, new_line=True)])
                            game_save.set_path_index(0)
                            stats = game_save.get_path()[0].find_object(
                                "Stats")
                            game_save.get_path().append(stats)
                            game_save.get_path().append(
                                stats.find_object("You"))
                            continue
                        _npc = game_save.get_path()[-1].get_attribute("NPC")
                        for npc in game_save.get_path()[0].find_object(
                                "Surroundings").get_objects():
                            if "NPC" in npc.get_attributes():
                                npc_name = npc.get_attribute(
                                    "NPC").get_name()
                                if npc_name == _npc.get_name():
                                    _npc = npc.get_attribute("NPC")
                                    break
                            npc_num += 1
                    stats, _damages = attack.attack(_npc.get_health(), is_you,
                                                    game_save.boost_attacks(),
                                                    game_save.accurate)

                    taming = False
                    for _damage in _damages:
                        if _damages[_damage] > 0:
                            if _damage == "Tame" or _damage == "Tame+" or _damage == "Tame2+":
                                taming = True
                    if not taming:
                        _npc.target = "You"
                    # Update npc health
                    _npc.set_health(stats)
                    # Update npc tooltip
                    if is_you:
                        game_save.update_stats_tooltip()
                    else:
                        game_save.get_path()[-1].set_tooltip(
                            _npc.gen_desc())

                    if "Disarm" in attack.get_attributes():
                        if len(_npc.weapons) > 0:
                            disarmed_weapon = random.choice(
                                list(filter(lambda x: "Body" not in
                                                      x.get_attributes(),
                                            _npc.weapons)))
                            _npc.weapons.remove(disarmed_weapon)
                            disarmed_weapon.reset_cooldowns()
                            game_save.get_path()[0].find_object(
                                "Surroundings").add_object(GameObject(
                                [Text(disarmed_weapon.get_name(),
                                      color=weapon_color,
                                      tooltip=disarmed_weapon.gen_desc())],
                                attributes={
                                    "Item": disarmed_weapon,
                                }, _actions=[GameAction("Pick-up")]))
                    if attack.ammo > 0 and attack.current_ammo < 1:
                        multiattack = False
                        game_save.drop_empty(weapon_object, attack_object)
                    if "Weapon" in attack_object.get_attributes():

                        weapon_object = attack_object.get_attribute(
                            "Weapon")
                        weapon = weapon_object.get_attribute("Item")
                        if "Thrown" in attack.get_attributes():
                            reset_cooldowns = True
                            game_save.add_ground_item(weapon_object)
                            game_save.remove_inv_item(weapon_object)
                        elif "Consumable" in attack.get_attributes():
                            game_save.remove_inv_item(weapon_object)
                    else:
                        weapon = None
                    if "Speed" in attack.get_attributes():
                        power = True
                        # Allow reduce cooldown weapons
                        for _weapon in _npc.weapons:
                            for _attack in _weapon.attacks:
                                _attack.decrease_cooldown()
                        for _attack in _npc.attacks:
                            _attack.decrease_cooldown()
                    if "Cryptic+" in attack.get_attributes():
                        power = True
                        game_save.cryptic = True
                        game_save.temp_cryptic = False
                        game_save.rename_flowers()
                    if "Orpheus" in attack.get_attributes():
                        power = True
                        game_save.orpheus = True
                    if "Calm" in attack.get_attributes():
                        power = True
                        _npc.target = None
                        _npc.aggressiveness /= 2
                    if "Enrage" in attack.get_attributes():
                        power = True
                        _npc.target = None
                        for _attack in _npc.attacks:
                            _attack *= 2
                    if "Herbal" in attack.get_attributes():
                        if not game_save.cryptic:
                            game_save.temp_cryptic = True
                            power = True
                        else:
                            add_event([Text("You already have this knowledge.",
                                            color=prompt_color, new_line=True)])
                            cancel = True
                    if "Camo" in attack.get_attributes():
                        power = True
                        game_save.camo = True
                    if "Camo+" in attack.get_attributes():
                        power = True
                        game_save.camo_plus = True
                    if "Trumpet" in attack.get_attributes():
                        power = True
                        npc_word_list = game_save.gen_npcs(
                            True)  # Allow for wandering monsters
                        for npc_word in npc_word_list:
                        #   if not isinstance(npc_word, type(None)):
                            add_event([npc_word, Text("wandered into the vicinity at the call of the trumpet.",
                                                            new_line=True,
                                                            color=treasure_color)])
                    if "Atlas" in attack.get_attributes():
                        location_choices = []
                        probabilities = [l.probability for l in LOCATIONS_LIST]
                        while True:
                            location = weighted_choice(LOCATIONS_LIST,
                                                       probabilities)
                            if location not in location_choices:
                                location_choices.append(location)
                            if len(location_choices) == 5:
                                break
                        choice = get_from_choices(
                            "Choose a location to travel to.", list(
                                location.get_type() for location in list(
                                    filter(lambda x: x.get_type() != "Olympus",
                                           location_choices))))
                        game_save.gen_location(choice)
                        display_location()
                    if "Sleep" in attack.get_attributes() and not game_save.orpheus:
                        game_save.sleep = 5
                        power = True
                        add_event([Text("You have fallen asleep.",
                                        new_line=True, color=prompt_color)])
                    elif "Sleep" in attack.get_attributes() and game_save.orpheus:
                        game_save.orpheus = False
                        add_event([Text("The music of Orpheus\\'s Lyre keeps you alert. Its effects have now worn off.",
                                        new_line=True, color=prompt_color)])
                    if "Omphalos" in attack.get_attributes():
                        game_save.gen_location("Olympus")
                        display_location()
                    if "Travel" in attack.get_attributes():
                        game_save.gen_location("Town")
                        display_location()
                    if "Thread" in attack.get_attributes():
                        game_save.gen_location("Labyrinth")
                        display_location()
                    if "Chains" in attack.get_attributes():
                        power = True
                        if "Chained" not in _npc.get_attributes():
                            _npc.add_attribute("Chained", True)
                            add_event([Text("Unbreakable chains wrap around them "
                                            "and they can't move.", new_line=True,
                                            color=prompt_color)])
                            if "Fox" in _npc.get_attributes():
                                _npc.set_health(starting_health)
                    if "Sleep+" in attack.get_attributes() and not game_save.orpheus:
                        game_save.sleep = 10
                        power = True
                        add_event(
                            [Text("You have fallen into a deep sleep.",
                                  new_line=True, color=prompt_color)])
                    if "Rod" in attack.get_attributes():
                        if game_save.get_health()["Brute"] < 3000:
                            game_save.get_health()["Brute"] += 100
                        if game_save.get_health()["Sharp"] < 2000:
                            game_save.get_health()["Sharp"] += 100
                        if game_save.get_health()["Burn"] < 1000:
                            game_save.get_health()["Burn"] += 100
                        if game_save.get_health()["Freeze"] < 1500:
                            game_save.get_health()["Freeze"] += 100
                        if game_save.get_health()["Agility"] < 400:
                            game_save.get_health()["Agility"] += 50
                        game_save.update_stats_tooltip()
                    elif "Sleep+" in attack.get_attributes() and game_save.orpheus:
                        game_save.orpheus = False
                        add_event([Text("The music of Orpheus\\'s Lyre keeps you alert. Its effects have now worn off.",
                                        new_line=True, color=prompt_color)])
                    if "Spawn-Weapon" in attack.get_attributes():
                        power = True
                        random_weapon = random.choice(WEAPONS_LIST)
                        random_weapon = GameObject(
                            [Text(random_weapon.get_name(),
                                  color=weapon_color,
                                  tooltip=random_weapon.gen_desc())],
                            attributes={
                                "Item": random_weapon,
                            }, _actions=[GameAction("Pick-Up")])
                        game_save.add_ground_item(random_weapon)
                        game_save.rename_flowers()
                 #   if "Spawn-Attack" in attack.get_attributes():
                  #      power = True
                   #     random_attack = dict_to_attack(random.choice(all_attacks))
                    #    game_save.get_character().add_attacks(
                     #       random_attack)
                    if "Spawn-Finely-Cooked-Meal" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(finely_cooked_meal.get_name(),
                                  color=weapon_color,
                                  tooltip=finely_cooked_meal.gen_desc())],
                            attributes={"Item": finely_cooked_meal,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You created a finely cooked meal.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Meal" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(cooked_meal.get_name(),
                                  color=weapon_color,
                                  tooltip=cooked_meal.gen_desc())],
                            attributes={"Item": cooked_meal,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You created a cooked meal.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Mead2" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(mead.get_name(),
                                  color=weapon_color,
                                  tooltip=mead.gen_desc())],
                            attributes={"Item": mead,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        game_save.add_inv_item(item)
                        add_event([Text("You obtained some mead.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Dagger" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(dagger.get_name(),
                                  color=weapon_color,
                                  tooltip=dagger.gen_desc())],
                            attributes={"Item": dagger,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You unsheath a dagger.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Treasure" in attack.get_attributes():
                        power = True
                        item = GameObject(
                            [Text(gold_ingot.get_name(),
                                  color=treasure_color,
                                  tooltip=gold_ingot.gen_desc())],
                            attributes={'Treasure': True,
                                        "Item": gold_ingot,
                                        }, _actions=[GameAction("Drop")])
                        game_save.add_inv_item(item)
                        add_event([Text("You create a gold ingot.",
                                        color=prompt_color, new_line=True)])
                    if "Spawn-Anything" in attack.get_attributes():
                        power = True
                        random_object = random.choice(EVERYTHING_LIST)
                        if type(random_object) != NPC:
                            random_object = GameObject(
                                [Text(random_object.get_name(),
                                      color=weapon_color,
                                      tooltip=random_object.gen_desc())],
                                attributes={
                                    "Item": random_object,
                                }, _actions=[GameAction("Pick-Up")])
                            game_save.add_ground_item(random_object)
                            game_save.rename_flowers()
                        else:
                            game_save.gen_npc(random_object)
                    if "Spawn-Spartae" in attack.get_attributes():
                        power = True
                        game_save.gen_npc(spartae)
                    if "Cow" in attack.get_attributes():
                        power = True
                        game_save.gen_npc(minotaur)
                    if "Self" in attack.get_attributes():
                        power = True

                    is_dead, tame, life = _npc.check_if_dead()

                    if len(_damages) >= 1 or power:
                        # Hit
                        game_save.hit_event(attack, _damages, "You", weapon,
                                            is_dead, tame, _npc)
                    else:
                        game_save.miss_event(attack, _damages, "You",
                                             weapon,
                                             _npc)

                    if not isinstance(disarmed_weapon, type(None)):
                        add_event([_npc.get_word(), Text("dropped "),
                                   Text(disarmed_weapon.get_name(),
                                        color=weapon_color,
                                        tooltip=disarmed_weapon.gen_desc(),
                                        new_line=True)])

                    if is_dead and not is_you:
                        if isinstance(_npc.attributes, type(None)):
                            _npc.attributes = []
                        if "Unique" in _npc.get_attributes():
                            game_save.uniques.append(_npc.get_type())
                        if "Smite" in _npc.attributes and not tame:
                            add_event([Text(
                                "Zeus smote you with a lightning bolt.",
                                color=dead_color, new_line=True)])
                            game_save.get_health()["Divine"] = 0
                            if not game_save.check_egg():
                                break  # Died, break loop
                        if "Grendel" in _npc.attributes and not tame:
                            item = GameObject(
                                [Text(hrothgars_gift.get_name(),
                                      color=weapon_color,
                                      tooltip=hrothgars_gift.gen_desc())],
                                attributes={"Item": hrothgars_gift,
                                            }, _actions=[GameAction("Drop")])
                            game_save.add_inv_item(item)
                            add_event([Text(
                                "Hrothgar is pleased. He presents you with a beautiful blade.",
                                color=prompt_color, new_line=True)])
                        if "Ophiotaurus" in _npc.attributes and not tame and _npc.get_health()["Burn"] <= 0:
                            item = GameObject(
                                [Text(adamantine_blade.get_name(),
                                      color=weapon_color,
                                      tooltip=adamantine_blade.gen_desc())],
                                attributes={"Item": adamantine_blade,
                                            }, _actions=[GameAction("Drop")])
                            game_save.add_inv_item(item)
                        for weapon in game_save.get_path()[0].find_object(
                                "Surroundings").get_objects()[
                            npc_num].get_objects():
                            weapon_item = weapon.get_attribute("Item")
                            if "Retained" not in weapon_item.get_attributes() \
                                    and not life and not tame:
                                # Dont drop if killed with life, tame, or is retained
                                weapon.add_action(GameAction("Pick-up"))
                                weapon_item.reset_cooldowns()
                                game_save.get_path()[0].find_object(
                                    "Surroundings").add_object(weapon)

                        current_npc = game_save.get_path()[0].find_object(
                            "Surroundings").get_objects()[npc_num]

                        if tame:
                            game_save.get_character().add_attacks(
                                current_npc.get_attribute("NPC").get_attacks(
                                    True))
                        else:
                            game_save.drop_ambrosia(_npc)

                        # Remove npc
                        game_save.get_path()[0].find_object("Surroundings"). \
                            remove_object(current_npc)
                        game_save.set_path_index(
                            len(game_save.get_path()) - 2)
                    else:
                        # Was not killed
                        if not is_you:
                            if "Fox" in _npc.get_attributes() and "Chained" not in _npc.get_attributes():
                                # Fox flees if attacked
                                add_event([_npc.get_word(),
                                           Text("fled.", color=dead_color,
                                                new_line=True)])

                                game_save.get_path()[0].find_object(
                                    "Surroundings").remove_object(
                                    game_save.get_path()[
                                        -1])
                                game_save.set_path_index(
                                    len(game_save.get_path()) - 2)

                    game_save.update_surroundings()
                    if attack.current_cooldown < 0:
                        multiattack = True
                    if attack.cooldown < 0 and attack.current_cooldown == 0:
                        attack.current_cooldown = attack.cooldown
                        multiattack = False
                    if attack.current_cooldown > 0:
                        game_save.open_path("Surroundings")
                        add_event([Text("Must wait ", color=prompt_color),
                                   Text(str(
                                       attack.current_cooldown - 1) + " ",
                                        color=number_color), Text(
                                "more turns until attack is ready to be used.",
                                new_line=True, color=prompt_color)])
                    if not cancel:
                        choosing = False

            if game_save.update_attacks():
                multiattack = False

            if reset_cooldowns:
                attack.reset_cooldown()

        if not multiattack and not cancel:
            game_save.tick()

        if not game_save.new_location:
            npc_word_list = game_save.gen_npcs(
                True)  # Allow for wandering monsters
            for npc_word in npc_word_list:
                if not isinstance(npc_word, type(None)):
                    add_event([npc_word, Text("wandered into the vicinity.",
                                              new_line=True,
                                              color=dead_color)])
        game_save.new_location = False

        display_objects()
        time.sleep(.01)
        if not is_running():
            break

    file_name = game_save.name + "." + extension
    if is_running():
        add_event(
            [Text("YOU ARE DEAD", color=dead_color, font_name=dead_font,
                  font_size=dead_font_size, new_line=True)])

        # TODO: Add boss fight
        game_save.set_path_index(0)
        game_save.get_path()[0].clear_objects()
        game_save.get_path()[0].clear_actions()
        display_objects()
        set_dead()

        if not game_save.dead:
            rename_file(file_name)
        game_save.dead = True
        save_to_file(game_save, "dead - " + file_name)
    else:
        save_to_file(game_save, file_name)

    # TODO: 'AOE' att on attacks: Area of effect
    # TODO: Display worn items ('Worn' Attribute)
    # TODO: Buy with treasure at towns
    # TODO: Read/Write stories with book
    # TODO: '?' att on weapon chooses random attack to use
    # TODO: Allow use straight from ground/inventory for self items
    # TODO: Add multiplayer
    # TODO: River Stix makes you invincible except for one random health
    # TODO: 'Unique' att on items

# TODO: Auto switch to same type of weapon if one runs out of ammo
# TODO: Make ambrosia yellow, flowers/rocks green
# TODO: Create actual initiative system. Integrate with wandering as well
Start the actual Game # TODO: Store lines out of screen past configurable char limit in variables # Game Object Classes Stores object traits      :type text_list: None | list[Text] | str Returns entire text list as a single string Returns Text object with intact attributes and all text from list Stores information related to in-game actions      :type text_list: None | list[Text] | str     :type _actions: list[GameAction]     :type _objects: list[GameObjects] Adds objects and actions to the tooltip # Save Classes Stores all game values # Equip first attack by default # For showing/hiding objects Clears actions and adds starting actions # If weapon is lost, set all identical weapons to same cooldown for continued multiuse # Update all cooldowns of same weapons/attacks # Find same weapon/attack to equip instead # Same attack name, same weapon name # Different attack # pth = cap_all_words(pth, [">", "-", ":"]) Clears the inventory and adds starting weapons Additive effect Multiplicative effect # Reduce agility # Increase agility Check if cannot leave # You were caught Chooses a random location to spawn in # Given name, find location # Gen items # Gen NPCs # Only spawn one random creature # Already been killed Creates an npc and adds to surroundings Returns NPCs in current surroundings Return Health Stats of self Sets the health stats for self Updates stats tooltip with current info # Found same type of weapon, allow continued attack # End multiattack turn because weapon was lost # Target is a friend to all # Shares a team # Do not add enemy to teams unless attacking a teammate of player's # Find target based on chosen target name # Add to ground # Remove from inventory # Remove random weapon # Hit # Target was killed # remove inv of observed npc # No targets # Add Attack name to attack tooltip # Add attack stats to attack tooltip Function to start gameplay # TODO: Hotkey's/Shortcuts box? # TODO: Choose an object and see its actions # Interface functions Adds an event to the events box          :type _event: str | list[Text]         :param _event: "" for new line Creates a prompt and waits for input          :type prompt: str | None         :type display_answer: bool         :type allow_path_change: bool         :type constrict: bool         :type hotkeys: bool # Main thread quit Returns the object or action chosen based on given name          :type _choice: str         :rtype: GameObject | GameAction | None # Down a path # raise Exception("Could not find choice: " + _choice) Sets the choices to cycle between # Get Path and reduce font size # Get chosen object and reduce font size # Add newline and add to events Gets user input based on current objects/actions          :type prompt: None | str         :type display_answer: bool         :type return_string: bool         :type allow_path_change: bool         :type choose_action: bool # Display Prompt # Main thread quit # Path was not changed # Path moved back via delete Returns the users response to a yes/no question          :type prompt: None | str         :type display_answer: bool         :type choices: None | list[str]         :param choices: Provide two choices in a list. 'Yes'/'No' if None Prompts the user to press 'Enter' Sends the current objects to the gui # Object selected # Clear box if empty # Clear textbox on first word # Clear textbox on first word # Display path # Game Functions Displays the tutorial # TODO: Update. Add Gameplay Save or load a game # Get all save files # Start game # Spawn Location # Main game loop # Main game loop # Combat # Died, break loop # Died, break loop # Choices # Main choice loop # Game exit # Died, break loop # Died, break loop # Add to inventory # Add to inventory # Add to inventory # Add to inventory # Add to inventory # Add to inventory # Remove from ground # Remove from Path # Add to ground # Remove from inventory # Remove from Path # Objects eval to True # Was caught # Escaped # If multiattacking, end turn # Get equipped attack # Get Attack from object # Check if attack has cooldown # Get target npc # Update npc health # Update npc tooltip # Allow reduce cooldown weapons # Allow for wandering monsters #   if not isinstance(npc_word, type(None)): #   if "Spawn-Attack" in attack.get_attributes(): #      power = True #     random_attack = dict_to_attack(random.choice(all_attacks)) #    game_save.get_character().add_attacks( #       random_attack) # Hit # Died, break loop # Dont drop if killed with life, tame, or is retained # Remove npc # Was not killed # Fox flees if attacked # Allow for wandering monsters # TODO: Add boss fight # TODO: 'AOE' att on attacks: Area of effect # TODO: Display worn items ('Worn' Attribute) # TODO: Buy with treasure at towns # TODO: Read/Write stories with book # TODO: '?' att on weapon chooses random attack to use # TODO: Allow use straight from ground/inventory for self items # TODO: Add multiplayer # TODO: River Stix makes you invincible except for one random health # TODO: 'Unique' att on items # TODO: Auto switch to same type of weapon if one runs out of ammo # TODO: Make ambrosia yellow, flowers/rocks green # TODO: Create actual initiative system. Integrate with wandering as well