diff --git a/requirements.txt b/requirements.txt index 50974d6..68eb7f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,8 @@ beautifulsoup4==4.12.2 +colorama==0.4.6 lxml==4.9.2 +markdown-it-py==3.0.0 +mdurl==0.1.2 +Pygments==2.15.1 +rich==13.4.2 soupsieve==2.4.1 diff --git a/sheditor.py b/sheditor.py index 59ef560..7d19e00 100644 --- a/sheditor.py +++ b/sheditor.py @@ -1,60 +1,76 @@ -import sys, argparse, copy, os, datetime +import argparse +import copy +import os +import datetime from bs4 import BeautifulSoup +import rich +from rich.console import Console + +# Only "normal storage" is used to insert requested items +normal_storage_ids = [82, 632] + +storage_ids = { + 82: {"name": "Small storage", "capacity": 50}, + 632: {"name": "Large storage", "capacity": 250}, + 3062: {"name": "Body storage", "capacity": 20}, +} + char_skills = { - 1 : "Piloting", # Likely deprecated - 2 : "Mining", - 3 : "Botany", - 4 : "Construction", - 5 : "Industry", - 6 : "Medical", - 7 : "Gunner", - 8 : "Shielding", - 9 : "Operations", - 10 : "Weapons", - 11 : "Unknown-1", - 12 : "Logistics", # All characters seem to have this but it doesn't show up in the UI - 13 : "Unknown-2-enigma", # All characters seem to have this skill but what is it for? - 14 : "Navigation", - 15 : "Unknown-3", - 16 : "Research", - 22 : "Piloting", # Appears to be the new piloting code? + 1: "Piloting", # Likely deprecated - now using code 22 + 2: "Mining", + 3: "Botany", + 4: "Construction", + 5: "Industry", + 6: "Medical", + 7: "Gunner", + 8: "Shielding", + 9: "Operations", + 10: "Weapons", + 11: "Unknown-1", + 12: "Logistics", # All characters seem to have this but it doesn't show up in the UI + 13: "Unknown-2-enigma", # All characters seem to have this skill but what is it for? + 14: "Navigation", + 15: "Unknown-3", + 16: "Research", + 22: "Piloting", # Appears to be the new piloting code? } char_attributes = { - 210 : "Bravery", - 214 : "Preception", - 213 : "Intelligence", - 212 : "Zest", + 210: "Bravery", + 214: "Preception", + 213: "Intelligence", + 212: "Zest", } char_traits = { - "Iron Stomach" : 1533, - "Spacefarer" : 1045, - "Confident" : 1046, - "Hard Working" : 1041, - "Antisocial" : 1037, - "Nyctophilia" : 1534, - "Wimp" : 655, - "Clumsy" : 656, - "Charming" : 1048, - "Bloodlust" : 1036, - "Fast Learner" : 1039, - "Minimalist" : 1535, - "Lazy" : 1040, - "Neurotic" : 1047, - "Alien Lover" : 2082, - "Needy" : 1038, - "Peace-loving" : 1043, - "Suicidal" : 1034, - "Smart" : 1035, - "Psychopath" : 1042, - "Hero" : 191, - "Talkative" : 1560, - "Iron-Willed" : 1044, - "Gourmand" : 1562, + "Iron Stomach": 1533, + "Spacefarer": 1045, + "Confident": 1046, + "Hard Working": 1041, + "Antisocial": 1037, + "Nyctophilia": 1534, + "Wimp": 655, + "Clumsy": 656, + "Charming": 1048, + "Bloodlust": 1036, + "Fast Learner": 1039, + "Minimalist": 1535, + "Lazy": 1040, + "Neurotic": 1047, + "Alien Lover": 2082, + "Needy": 1038, + "Peace-loving": 1043, + "Suicidal": 1034, + "Smart": 1035, + "Psychopath": 1042, + "Hero": 191, + "Talkative": 1560, + "Iron-Willed": 1044, + "Gourmand": 1562, } + class ItemCodeDatabase: def __init__(self, database_filename): @@ -76,7 +92,7 @@ class ItemCodeDatabase: # A single item with its code, name, and quantity class Item: - + def __init__(self, code, name, quantity): self.code = code self.name = name @@ -85,9 +101,10 @@ class Item: # A single storage area, with a list of contents (Items) class StorageArea: - + def __init__(self, tag): self.tag = tag + self.type_id = None self.items = [] self.is_abnormal_storage = False @@ -103,7 +120,7 @@ class StorageArea: class Character: - + def __init__(self, name, tag): self.name = name self.tag = tag @@ -148,15 +165,24 @@ class Character: return new_char def print_summary(self): - print("Name: {}".format(self.name)) - print() - print("Skills:") - for skill in self.skills: - print("{} : {}".format(char_skills[skill['id']], skill['level'])) - print() - print("Attributes:") - for attribute in self.attributes: - print("{} : {}".format(char_attributes[attribute['id']], attribute['points'])) + console = Console(highlight=False) + console.print("Name: [bright_cyan]{}[/]".format(self.name)) + console.print() + console.print("Skills:") + for i, skill in enumerate(self.skills): + row_string = "{:20} [bright_cyan]{:2}[/]".format(char_skills[skill['id']], skill['level']) + if i % 2 == 0: + console.print(" [on #222222]{}[/]".format(row_string)) + else: + console.print(" {}".format(row_string)) + console.print() + console.print("Attributes:") + for i, attribute in enumerate(self.attributes): + row_string = "{:12} [bright_cyan]{:2}[/]".format(char_attributes[attribute['id']], attribute['points']) + if i % 2 == 0: + console.print(" [on #222222]{}[/]".format(row_string)) + else: + console.print(" {}".format(row_string)) def __repr__(self): return "{} - Skills: {} - Attributes: {}".format(self.name, repr(self.skills), repr(self.attributes)) @@ -168,7 +194,7 @@ class Ship: self.name = name self.owner = owner self.state = state - self.tag = tag # Soup tag corresponding to this ship's node + self.tag = tag # Soup tag corresponding to this ship's node self.storage_areas = [] self.characters = [] @@ -223,6 +249,7 @@ class GameData: ship_name = ship_tag['sname'] # print(ship_name) + # Forgotten - why did I specify owner as required here - what ships don't have owners? owner_node = ship_tag.find('settings', owner=True) ship_owner = owner_node['owner'] ship_state = owner_node['state'] @@ -233,8 +260,10 @@ class GameData: # Step 3 - Storage area data for inv_tag in ship_tag.find_all('feat', eatAllowed=True): storage_area = StorageArea(inv_tag.find('inv')) - # This is a GUESS - if inv_tag.find('env'): + # 632 is the "m" id code for LARGE storage + storage_area.type_id = int(inv_tag.parent.parent['m']) + if storage_area.type_id not in normal_storage_ids: # TODO: Un-hardcode this AND take into account SMALL storage + # rich.print("[on #222222]Found abnormal storage id [#666666]{}[/][/]".format(inv_tag.parent.parent['m'])) storage_area.is_abnormal_storage = True ship.add_storage_area(storage_area) # Items within storage area @@ -248,7 +277,7 @@ class GameData: # Step 4 - Character data for character_list in ship_tag.find_all('characters'): - character_tags = character_list.find_all('c', attrs={'name':True}) + character_tags = character_list.find_all('c', attrs={'name': True}) for character_tag in character_tags: char_name = character_tag['name'] character = Character(char_name, character_tag) @@ -263,11 +292,11 @@ class GameData: skill_exp = sk_tag['exp'] skill_expd = sk_tag['expd'] skill_dict = { - 'id' : int(skill_id), - 'level' : int(skill_level), - 'mxn' : int(skill_mxn), - 'exp' : int(skill_exp), - 'expd' : int(skill_expd), + 'id': int(skill_id), + 'level': int(skill_level), + 'mxn': int(skill_mxn), + 'exp': int(skill_exp), + 'expd': int(skill_expd), } skills.append(skill_dict) character.set_skills(skills) @@ -278,13 +307,12 @@ class GameData: attribute_id = a_tag['id'] attribute_points = a_tag['points'] attribute_dict = { - 'id' : int(attribute_id), - 'points' : int(attribute_points), + 'id': int(attribute_id), + 'points': int(attribute_points), } attributes.append(attribute_dict) character.set_attributes(attributes) - def writeback(self): def replace_id(dict, old_key, new_key): dict_copy = dict.copy() @@ -297,10 +325,10 @@ class GameData: # Shouldn't need to update player tag as this will be done directly # Only need to update things with structure / lists - + """ - Step 1 - Update characters - Step 2 - Update storage areas + Step 1 - Update characters + Step 2 - Update storage areas """ # Step 1 - Update characters @@ -333,7 +361,7 @@ class GameData: area_tag = storage_area.tag area_tag.clear() for item in storage_area.items: - tag_dict = { 'elementaryId' : item.code, 'inStorage' : item.quantity, 'onTheWayIn' : 0, 'onTheWayOut' : 0} + tag_dict = {'elementaryId': item.code, 'inStorage': item.quantity, 'onTheWayIn': 0, 'onTheWayOut': 0} new_tag = self.soup.new_tag('s', attrs=tag_dict) area_tag.append(new_tag) @@ -364,46 +392,59 @@ class GameData: return def print_detailed_character_summary(self): + console = Console(highlight=False) for ship in self.ships: + console.print("Listing characters for ship [magenta]{}[/] (owned by [bright_cyan]{}[/], state [bright_cyan]{}[/]):".format(ship.name, ship.owner, ship.state)) if len(ship.characters) == 0: - print("Ship {} (owned by {}, state {}) has no characters, skipping...".format(ship.name, ship.owner, ship.state)) - print() + console.print(" This ship has no characters, skipping...") + console.print() + console.print() continue - print("Listing characters for ship {} (owned by {}, state {}):".format(ship.name, ship.owner, ship.state)) - print('-----') - for character in ship.characters: character.print_summary() - print('-----') + console.print('-----') - print() + console.print() def print_detailed_item_summary(self): + console = Console(highlight=False) for ship in self.ships: + rich.print("Inventory for ship [#aa00ff]{}[/] (owned by [bright_cyan]{}[/], state [bright_cyan]{}[/]):".format(ship.name, ship.owner, ship.state)) if len(ship.storage_areas) == 0: - print("Ship {} (owned by {}, state {}) has no storage areas, skipping...".format(ship.name, ship.owner, ship.state)) - print() + console.print(" This ship has no storage areas, skipping...") + console.print() + console.print() continue - print("Inventory for ship {} (owned by {}, state {})".format(ship.name, ship.owner, ship.state)) for index, storage_area in enumerate(ship.storage_areas): + console.print(" Storage area [bright_cyan]{}[/] (type: [bright_cyan]{}[/], abnormal storage: [bright_cyan]{}[/]):".format(index, storage_ids[storage_area.type_id]["name"], storage_area.is_abnormal_storage)) if len(storage_area.items) == 0: - print(" Storage area {} is empty.".format(index)) + console.print(" This storage area is empty.") + console.print() continue - print(" Storage area {} (abnormal storage: {}):".format(index, storage_area.is_abnormal_storage)) - for item in storage_area.items: - print(" {:4}: {} - {}".format(item.code, item.name, item.quantity)) - - print() - print() + console.print("[bold] {:>4} {:30} {:>3}[/]".format("ID", "Name", "#")) + total_quantity = 0 + for i, item in enumerate(storage_area.items): + row_string = " {:4} {:30} {:3}".format(item.code, item.name, item.quantity) + total_quantity += item.quantity + if i % 2 == 0: + console.print(" [on #222222]{}[/]".format(row_string)) + else: + console.print(" {}".format(row_string)) + storage_area_capacity = storage_ids[storage_area.type_id]["capacity"] + console.print(" Total quantity: [bright_cyan]{}[/]/[bright_cyan]{}[/]".format(total_quantity, storage_area_capacity)) + if total_quantity > storage_area_capacity: + console.print(" [dark_orange]WARNING: Storage capacity exceeded[/]") + console.print() + console.print() def print_summary(self): print("Start game summary:") print(" Player currency: {}".format(self.player.currency)) print(" Number of ships: {}".format(len(self.ships))) - + for ship in self.ships: print(" {} (owner {}, state {})".format(ship.name, ship.owner, ship.state)) print(" Contains {} storage area(s):".format(len(ship.storage_areas))) @@ -503,7 +544,6 @@ def inventory(soup, add_code, add_quantity): storage_space_counter += 1 print('-----') - print('Item total summary:') print() for item in item_tracking.items(): @@ -512,7 +552,7 @@ def inventory(soup, add_code, add_quantity): item_quantity = item[1] print('{:4} - {} - {}'.format(item_code, item_name, item_quantity)) - + def give_money(soup, amount): bank_tag = soup.find('playerBank') @@ -520,7 +560,7 @@ def give_money(soup, amount): def list_ships(soup): - + ship_tags = soup.find_all('ship') for ship_tag in ship_tags: @@ -538,9 +578,9 @@ def list_ships(soup): ship_owner = owner_node['owner'] ship_state = owner_node['state'] print('Ship found: {} owned by {} (state: {})'.format(ship_name, ship_owner, ship_state)) - # print(settings_node) + def parse_item_file(filename): results = [] @@ -591,7 +631,8 @@ def main(): soup = BeautifulSoup(full_text, "xml") item_code_database = ItemCodeDatabase('item_ids.txt') - print("Item code database successfully loaded") + print("Item code database successfully loaded.") + print() game_data = GameData(soup, item_code_database) game_data.populate() @@ -620,7 +661,7 @@ def main(): if args.add_item_set: item_list = parse_item_file(args.add_item_set[0]) # print(item_list) - print ("Items to be added:") + print("Items to be added:") for (item_code, item_quantity) in item_list: print("{} : {}".format(item_code_database.get_name_from_code(item_code), item_quantity)) game_data.add_item(item_code, item_quantity) @@ -635,9 +676,6 @@ def main(): game_data.writeback() if args.test_gamedata: - item_code_database = ItemCodeDatabase('item_ids.txt') - print("Item code database successfully loaded") - game_data = GameData(soup, item_code_database) game_data.populate() # game_data.add_item(1759, 100) @@ -675,5 +713,6 @@ def main(): f.write(sansfirstline) f.close() + if __name__ == "__main__": main()