215 lines
10 KiB
Python
215 lines
10 KiB
Python
import yaml
|
|
from yaml.loader import SafeLoader
|
|
from colorama import Fore, Back
|
|
import re
|
|
from lib.item import Item
|
|
|
|
from lib.menu import HasItemDialogue, MenuManager
|
|
from .save import SaveManager
|
|
from .ascii import AsciiAnimation
|
|
from .fight import *
|
|
from time import sleep
|
|
from os import system
|
|
|
|
class Game: # the game class keeps information about the loaded game
|
|
def __init__(self,data:dict,lang):
|
|
self.name = data["meta"]["name"] # Game name
|
|
self.author = data["meta"]["creator"] # Game creator
|
|
self.current = "start" # Current prompt
|
|
self.nodes = {} # All nodes
|
|
self.inventory = [] # Player's inventory
|
|
self.id = data["meta"]["id"] # Game ID
|
|
self.lang = lang # Language strings
|
|
self.save = SaveManager(self.id,self.lang) # saving
|
|
self.equipped = {"weapon":None,"armor":None} # Items equipped by player
|
|
self.enemies = {} # Enemies
|
|
if "equippable" in data["meta"].keys():
|
|
self.equippable = [] # Items that can be equipped by player
|
|
for item in data["meta"]["equippable"]:
|
|
name = list(item.keys())[0]
|
|
if "def" in item[name].keys() and "atk" in item[name].keys():
|
|
self.equippable.append(Item(name,item[name]["atk"],item[name]["def"]))
|
|
elif "def" in item[name].keys():
|
|
self.equippable.append(Item(name=name,defense=item[name]["def"]))
|
|
elif "atk" in item[name].keys():
|
|
self.equippable.append(Item(name,item[name]["atk"]))
|
|
if("starter" in item[name].keys()): # if starter, equip and add to inventory
|
|
if item[name]["starter"]:
|
|
i = next((x for x in self.equippable if x.name == list(item.keys())[0]))
|
|
self.inventory.append(i)
|
|
self.equipped[i.type] = i
|
|
if "enemies" in data["meta"].keys():
|
|
# Load enemies
|
|
for en in data["meta"]["enemies"]:
|
|
name = list(en.keys())[0]
|
|
self.enemies[name] = {"name":en["name"],"hp":en["hp"],"attacks":en["attacks"],"def":en["def"]}
|
|
for k in data["game"]:
|
|
self.nodes.update({k:data["game"][k]})
|
|
|
|
def main_menu(self): # displays the main menu
|
|
l = self.save.load()
|
|
if not l:
|
|
# New game
|
|
m = MenuManager([self.lang['start'],self.lang['options'],self.lang['quit']],f"{self.name}\n{self.lang['game_by'].replace('$author',self.author)}")
|
|
selection = m.selected
|
|
system("cls||clear")
|
|
if(selection == 0): # start new game
|
|
self.print_text()
|
|
elif(selection == 1):
|
|
self.settings_menu()
|
|
elif(selection == 2):
|
|
print(self.lang['quitting'])
|
|
exit()
|
|
else: # Display continue
|
|
m = MenuManager([self.lang['continue'],self.lang['new_game'],self.lang['options'],self.lang['quit']],f"{self.name}\n{self.lang['game_by'].replace('$author',self.author)}")
|
|
selection = m.selected
|
|
system("cls||clear")
|
|
if(selection == 0):
|
|
self.current = self.save.currentPrompt
|
|
self.inventory = self.save.inventory
|
|
self.print_text()
|
|
elif(selection == 1):
|
|
self.print_text()
|
|
elif(selection == 2):
|
|
self.settings_menu()
|
|
elif(selection == 3):
|
|
print(self.lang['quitting'])
|
|
exit()
|
|
|
|
def settings_menu(self): # displays the settings menu
|
|
m = MenuManager([self.lang['lang'],self.lang['back']],self.lang['options'])
|
|
selection = m.selected
|
|
if(selection == 0):
|
|
m = MenuManager(["English","Česky",self.lang['back']],self.lang['lang'])
|
|
selection = m.selected
|
|
system("cls||clear")
|
|
if(selection == 0):
|
|
with open("./saves/lang","w") as f:
|
|
f.write("en")
|
|
elif(selection == 1):
|
|
with open("./saves/lang","w") as f:
|
|
f.write("cz")
|
|
self.settings_menu()
|
|
else:
|
|
self.main_menu()
|
|
|
|
def print_text(self): # Prints out the current prompt
|
|
system("cls||clear")
|
|
if "add_item" in self.nodes[self.current].keys(): # if there is an add_inventory key in the node,
|
|
# add item to inventory
|
|
item = self.nodes[self.current]['add_item']
|
|
if item not in self.inventory:
|
|
self.inventory.append(item)
|
|
print(self.inventory)
|
|
system("clear||cls")
|
|
print(f"{self.lang['acquire'].replace('$item',f'{Fore.CYAN}{item}{Fore.RESET}')}")
|
|
sleep(3)
|
|
system("clear||cls")
|
|
animated = re.search(r"(?!{).+(?=})",self.nodes[self.current]["text"]) # find the animated text
|
|
if(animated != None):
|
|
self.print_animated(animated.group(0))
|
|
self.nodes[self.current]["text"] = self.nodes[self.current]["text"].replace("{"+animated.group(0)+"}","") # remove the animated text from the text prompt
|
|
if("actions" in self.nodes[self.current].keys()):
|
|
actions_desc = [] # has descriptions of text prompts, so that we don't need to find them in MenuManager
|
|
need_item = [] # helps implement a check for needing an item
|
|
for option in self.nodes[self.current]["actions"]:
|
|
try:
|
|
actions_desc.append(self.nodes[option]["description"])
|
|
if "has_item" in self.nodes[option].keys():
|
|
need_item.append(self.nodes[option]["has_item"])
|
|
else:
|
|
need_item.append(None)
|
|
except Exception:
|
|
print(f"{Back.RED}{Fore.WHITE}{self.lang['no_action'].replace('$action',option)}{Fore.RESET}")
|
|
exit(1)
|
|
m = ""
|
|
actions_desc.extend([self.lang['inventory'],self.lang['quit']])
|
|
if(all(element == None for element in need_item) is False):
|
|
need_item.extend([None, None])
|
|
# we need to check if user has item
|
|
m = HasItemDialogue(actions_desc,self.parse_colors(self.nodes[self.current]["text"]),self.inventory,need_item)
|
|
while need_item[m.selected] != None and all(element not in self.inventory for element in need_item[m.selected]): # until user selects an available prompt, re-prompt again
|
|
m = HasItemDialogue(actions_desc,self.parse_colors(self.nodes[self.current]["text"]),self.inventory,need_item)
|
|
if m.selected <= len(actions_desc)-3 and "has_item" in self.nodes[self.nodes[self.current]["actions"][m.selected]].keys():
|
|
for item in need_item[m.selected]:
|
|
self.inventory.remove(item)
|
|
elif "fight" in self.nodes[self.current].keys():
|
|
# Initiate a fight
|
|
enemy = self.enemies[self.nodes[self.current]["fight"]] # TODO: Complete after fight actions
|
|
m = FightHandler(self.nodes[self.current]["text"],enemy["name"],enemy["hp"],enemy["def"],enemy["attacks"],self.lang,self.equipped,self.inventory)
|
|
input()
|
|
while m.hp > 0 and m.my > 0:
|
|
m.show()
|
|
m.rebind() # rebind due to MenuManager in show_inventory
|
|
input()
|
|
system("cls||clear")
|
|
keyboard.remove_all_hotkeys()
|
|
if m.hp < 1:
|
|
# Enemy defeated
|
|
print(self.lang["defeated"].replace("$enemy",enemy["name"]))
|
|
sleep(5)
|
|
self.current = self.nodes[self.current]["actions"][0] # move to the first action
|
|
self.print_text()
|
|
else:
|
|
# Player defeated
|
|
print(self.lang["defeat"].replace("$enemy",enemy["name"]))
|
|
sleep(5)
|
|
self.print_text()
|
|
return
|
|
else:
|
|
m = MenuManager(actions_desc,self.parse_colors(self.nodes[self.current]["text"]))
|
|
sel = m.selected
|
|
if(sel == len(actions_desc)-2): # show inventory
|
|
self.show_inventory()
|
|
elif (sel == len(actions_desc)-1): # Save & quit
|
|
self.save.currentPrompt = self.current # save the current prompt
|
|
self.save.inventory = self.inventory
|
|
self.save.save()
|
|
exit(0)
|
|
else:
|
|
self.current = self.nodes[self.current]["actions"][sel]
|
|
self.print_text()
|
|
else:
|
|
print(self.parse_colors(self.nodes[self.current]["text"]))
|
|
print("")
|
|
|
|
def show_inventory(self):
|
|
if len(self.inventory) == 0:
|
|
MenuManager([self.lang["return"]],f" {self.lang['inside_inv']} \n")
|
|
else:
|
|
s = ""
|
|
for i,item in enumerate(self.inventory):
|
|
if type(item) is Item:
|
|
if(i == len(self.inventory)): # last item
|
|
s += f"- {item.name}"
|
|
else:
|
|
s += f"- {item.name}\n"
|
|
else:
|
|
if(i == len(self.inventory)): # last item
|
|
s += f"- {item}"
|
|
else:
|
|
s += f"- {item}\n"
|
|
MenuManager([self.lang["return"]],f" {self.lang['inside_inv']} \n{s}")
|
|
self.print_text()
|
|
|
|
def print_animated(self,animid): # prints the first found occurence of an ascii animation
|
|
animation = AsciiAnimation()
|
|
animation.load_ascii(animid)
|
|
animation.play()
|
|
print()
|
|
|
|
def parse_colors(self,text:str) -> str: # Converts color codes into terminal colors
|
|
newText = text.replace("&b",Fore.CYAN).replace("&c",Fore.RED).replace("&e", Fore.YELLOW).replace("&a",Fore.GREEN).replace("&9",Fore.BLUE).replace("&r",Fore.RESET).replace("&f",Fore.WHITE).replace("&5",Fore.MAGENTA).replace("\n",Fore.RESET + "\n") # replace color codes and newlines with colorama
|
|
newText += Fore.RESET # reset color at the end of the text
|
|
return newText
|
|
|
|
def load(file_path,lang): # starts to load the game from YAML
|
|
try:
|
|
with open(file_path) as f:
|
|
data = yaml.load(f,Loader=SafeLoader)
|
|
g = Game(data,lang)
|
|
return g
|
|
except Exception as e:
|
|
print(f"{Back.RED}{Fore.WHITE}ERROR{Fore.RESET}{Back.RESET}")
|
|
print(e)
|
|
return None
|