From b00912b1e12ed3b68947486d3928a17546c281cc Mon Sep 17 00:00:00 2001 From: Chris Davoren Date: Fri, 30 Jun 2023 12:37:25 +1000 Subject: [PATCH] Extensive additions to class structure. --- characters.py | 269 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 255 insertions(+), 14 deletions(-) diff --git a/characters.py b/characters.py index fa5eea8..9abd3d3 100644 --- a/characters.py +++ b/characters.py @@ -1,16 +1,74 @@ import sys, argparse from bs4 import BeautifulSoup +char_skills = { + 1 : "Piloting", + 2 : "Mining", + 3 : "Botany", + 4 : "Construction", + 5 : "Industry", + 6 : "Medical", + 7 : "Gunner", + 8 : "Shielding", + 9 : "Operations", + 10 : "Weapons", + 12 : "Logistics", + 13 : "Unknown", + 14 : "Navigation", + 16 : "Research", +} + +char_attributes = { + 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, +} + class ItemCodeDatabase: def __init__(self, database_filename): - pass + self.id_dict = {} + + for line in open(database_filename): + result = line.split() + code = int(result[0]) + name = ' '.join(result[1:]) + # print(code, name) + self.id_dict[code] = name def get_name_from_code(self, code): - pass + return self.id_dict[code] def validate_code(self, code): - pass + return code in self.id_dict # A single item with its code, name, and quantity @@ -25,28 +83,36 @@ class Item: # A single storage area, with a list of contents (Items) class StorageArea: - def __init__(self): - pass + def __init__(self, tag): + self.tag = tag + self.items = [] def add_item(self, item): - pass + self.items.append(item) # Returns how full the storage area is based on its contents - def total_occupancy(self): - pass + def get_total_occupancy(self): + total_quantity = 0 + for item in self.items: + total_quantity += item.quantity + return total_quantity class Character: # Will need an internal sense of what the codes mean for string output purposes - def __init__(self, name): + def __init__(self, name, tag): self.name = name + self.tag = tag - def set_skills(self, skill_array): - pass + self.skills = {} + self.attributes = {} - def set_attributes(self, attribute_array): - pass + def set_skills(self, skill_list): + self.skills = skill_list + + def set_attributes(self, attribute_list): + self.attributes = attribute_list def maximize_skills(self): pass @@ -54,7 +120,146 @@ class Character: def maximize_attributes(self): pass -# How will we get all this back into the XML file though?? + def clone(self, new_name): + pass + + def __repr__(self): + return "{} - {} - {}".format(self.name, repr(self.skills), repr(self.attributes)) + + +class Ship: + + def __init__(self, name, owner, state, tag): + self.name = name + self.owner = owner + self.state = state + self.tag = tag # Soup tag corresponding to this ship's node + + self.storage_areas = [] + self.characters = [] + + def add_storage_area(self, storage_area): + self.storage_areas.append(storage_area) + + def add_character(self, character): + self.characters.append(character) + + +class Player: + + def __init__(self, currency): + self.currency = currency + + +class GameData: + + def __init__(self, soup, item_database): + self.player = None + self.ships = [] + + self.soup = soup + self.item_database = item_database + + def populate(self): + # Step 1 - Player data + char_tag = self.soup.find('playerBank') + currency = char_tag['ca'] + self.player = Player(currency) + + # Step 2 - Ship data + ship_tags = self.soup.find_all('ship') + + for ship_tag in ship_tags: + ship_name = ship_tag['sname'] + + owner_node = ship_tag.find('settings', owner=True) + ship_owner = owner_node['owner'] + ship_state = owner_node['state'] + + ship = Ship(ship_name, ship_owner, ship_state, ship_tag) + self.ships.append(ship) + + # Step 3 - Storage area data + for inv_tag in ship_tag.find_all('feat', eatAllowed=True): + storage_area = StorageArea(inv_tag) + ship.add_storage_area(storage_area) + # Items within storage area + for s_tag in inv_tag.find_all('s'): + item_code = int(s_tag['elementaryId']) + item_name = self.item_database.get_name_from_code(item_code) + item_quantity = int(s_tag['inStorage']) + item = Item(item_code, item_name, item_quantity) + storage_area.add_item(item) + # print("{:4}: {} - {}".format(item_code, item_name, item_quantity)) + + # Step 4 - Character data + for character_list in ship_tag.find_all('characters'): + 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) + ship.add_character(character) + + skills = [] + skill_tag = character_tag.find('skills') + for sk_tag in skill_tag.find_all('s'): + skill_id = sk_tag['sk'] + skill_level = sk_tag['level'] + skill_mxn = sk_tag['mxn'] + 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), + } + skills.append(skill_dict) + character.set_skills(skills) + + attributes = [] + attribute_tag = character_tag.find('attr') + for a_tag in attribute_tag.find_all('a'): + attribute_id = a_tag['id'] + attribute_points = a_tag['points'] + attribute_dict = { + 'id' : int(attribute_id), + 'points' : int(attribute_points), + } + attributes.append(attribute_dict) + character.set_attributes(attributes) + + """ + Need to find player + Need to find ships + Within ships, need to find: + - Storage areas, then items within storage areas + - Characters, then names, skills, attributes etc + + """ + pass + + def writeback(self): + # Purpose of this mission is to take all our data and replace the relevant parts of the soup + # Suspect this may be harder than it sounds - original saved game editor more or less deleted and rewrote some sections (e.g. item lists) + pass + + 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))) + for index, storage_area in enumerate(ship.storage_areas): + print(" Storage area {} - contains {} item(s) - occupancy {} unit(s)".format(index, len(storage_area.items), storage_area.get_total_occupancy())) + print(" Has {} character(s):".format(len(ship.characters))) + for char in ship.characters: + print(" {}".format(char.name)) + print(" {}".format(repr(char))) + def characters(soup): for character in soup.find_all('characters'): @@ -160,6 +365,29 @@ def give_money(soup, amount): bank_tag['ca'] = amount +def list_ships(soup): + + ship_tags = soup.find_all('ship') + + for ship_tag in ship_tags: + ship_name = ship_tag['sname'] + + """ + settings_nodes = ship_tag.find_all('settings') + for settings_node in settings_nodes: + if settings_node.has_attr('owner'): + ship_owner = settings_node['owner'] + else: + continue + """ + owner_node = ship_tag.find('settings', owner=True) + 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 main(): parser = argparse.ArgumentParser(prog="Space Haven Saved Game Inspector", description="As above.") @@ -167,6 +395,8 @@ def main(): parser.add_argument('--add_item', required=False, metavar='N', type=int, nargs=2, help="Add more of an existing item to storage by CODE - refer to accompanying data file reference for codes. First number is the code, second is the desired quantity.") parser.add_argument('--buff_chars', required=False, action='store_true', help="For all characters, increases all skills and attributes to maximum. Use wisely.") parser.add_argument('--money', required=False, type=int, nargs=1, help="Give the player credits of the specified amount") + parser.add_argument('--list_ships', required=False, action='store_true', help="List all ships with names and their respective owners") + parser.add_argument('--test_gamedata', required=False, action='store_true', help="Test of new class-based system of storing game information") args = parser.parse_args() # print(args) @@ -202,6 +432,17 @@ def main(): print('Increasing money to the given amount...') give_money(soup, args.money[0]) + if args.list_ships: + list_ships(soup) + + 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.print_summary() + text = soup.prettify() # Delete XML header - game doesn't have it sansfirstline = '\n'.join(text.split('\n')[1:])