2023-07-11 07:26:52 +00:00
import argparse
import copy
import os
import datetime
2023-07-15 22:59:50 +00:00
import csv
2023-06-28 15:36:32 +00:00
from bs4 import BeautifulSoup
2023-07-15 23:39:00 +00:00
import rich . console
2023-07-11 07:26:52 +00:00
2023-08-28 23:14:30 +00:00
DEFAULT_SAVEGAMEPATH = " c: \\ Program Files (x86) \\ GOG Galaxy \\ Games \\ SpaceHaven \\ savegames \\ "
2023-07-11 07:26:52 +00:00
# Only "normal storage" is used to insert requested items
normal_storage_ids = [ 82 , 632 ]
2023-08-30 03:50:24 +00:00
weapon_ids = [ 726 , 725 , 3383 , 760 , 3069 , 3070 , 3071 , 3072 , 3384 ]
2023-07-11 07:26:52 +00:00
storage_ids = {
82 : { " name " : " Small storage " , " capacity " : 50 } ,
632 : { " name " : " Large storage " , " capacity " : 250 } ,
3062 : { " name " : " Body storage " , " capacity " : 20 } ,
2023-08-31 08:16:33 +00:00
3068 : { " name " : " Robot storage " , " capacity " : 20 } ,
2023-07-11 07:26:52 +00:00
}
2023-07-15 23:39:00 +00:00
console = rich . console . Console ( highlight = False )
2023-06-28 23:58:33 +00:00
2023-07-15 22:59:50 +00:00
# A single item with its code, name, and quantity
class Item :
2023-06-30 02:37:25 +00:00
2023-07-15 22:59:50 +00:00
item_ids = None
2023-06-28 23:58:33 +00:00
2023-07-15 22:59:50 +00:00
@classmethod
def load_ids ( cls , item_filename ) :
if cls . item_ids is not None :
return
else :
cls . item_ids = { }
2023-06-28 23:58:33 +00:00
2023-07-15 22:59:50 +00:00
with open ( item_filename , ' r ' ) as item_file :
item_reader = csv . reader ( item_file , delimiter = ' \t ' )
for row in item_reader :
cls . item_ids [ int ( row [ 0 ] ) ] = row [ 1 ] . strip ( )
2023-06-28 23:58:33 +00:00
2023-07-15 22:59:50 +00:00
@classmethod
def get_name_from_code ( cls , code ) :
return cls . item_ids . get ( code , None ) if cls . item_ids is not None else None
2023-06-28 23:58:33 +00:00
2023-07-15 22:59:50 +00:00
@classmethod
def validate_code ( cls , code ) :
return code in cls . id_dict if cls . item_ids is not None else False
2023-07-11 07:26:52 +00:00
2023-06-28 23:58:33 +00:00
def __init__ ( self , code , name , quantity ) :
self . code = code
self . name = name
self . quantity = quantity
2023-08-31 08:16:33 +00:00
2023-08-30 03:50:24 +00:00
class ResearchItem :
research_ids = None
@classmethod
def load_ids ( cls , item_filename ) :
if cls . research_ids is not None :
return
else :
cls . research_ids = { }
with open ( item_filename , ' r ' ) as item_file :
item_reader = csv . reader ( item_file , delimiter = ' \t ' )
for row in item_reader :
# console.print(row)
if len ( row ) == 0 or row [ 0 ] [ 0 ] == ' # ' :
continue
cls . research_ids [ int ( row [ 0 ] ) ] = row [ 1 ] . strip ( )
@classmethod
def get_name_from_code ( cls , code ) :
return cls . research_ids . get ( code , None ) if cls . research_ids is not None else None
2023-08-31 21:23:53 +00:00
def __init__ ( self , id , active_stage , paused , states , tag ) :
2023-08-30 03:50:24 +00:00
self . id = id
self . name = ResearchItem . get_name_from_code ( id )
2023-08-30 13:56:35 +00:00
self . paused = paused
2023-08-30 03:50:24 +00:00
self . active_stage = active_stage
2023-08-30 13:56:35 +00:00
self . stage_states = states
2023-08-31 21:23:53 +00:00
self . tag = tag
2023-08-30 03:50:24 +00:00
def __str__ ( self ) :
return " {} : {} : {} " . format ( self . id , self . name , self . active_stage )
2023-06-28 23:58:33 +00:00
2023-08-30 13:56:35 +00:00
class ResearchItemState :
def __init__ ( self , done , stage , blocksdone ) :
self . done = done
self . stage = int ( stage )
self . blocksdone = blocksdone
def __str__ ( self ) :
return " {} : {} : {} " . format ( self . done , self . stage , self . blocksdone )
2023-06-28 23:58:33 +00:00
# A single storage area, with a list of contents (Items)
class StorageArea :
2023-07-11 07:26:52 +00:00
2023-06-30 02:37:25 +00:00
def __init__ ( self , tag ) :
self . tag = tag
2023-07-11 07:26:52 +00:00
self . type_id = None
2023-06-30 02:37:25 +00:00
self . items = [ ]
2023-07-15 22:59:50 +00:00
self . is_normal_storage = False
2023-06-28 23:58:33 +00:00
def add_item ( self , item ) :
2023-06-30 02:37:25 +00:00
self . items . append ( item )
2023-06-28 23:58:33 +00:00
2023-08-31 21:23:53 +00:00
def get_capacity ( self ) :
return storage_ids [ self . type_id ] [ ' capacity ' ]
2023-06-28 23:58:33 +00:00
# Returns how full the storage area is based on its contents
2023-06-30 02:37:25 +00:00
def get_total_occupancy ( self ) :
total_quantity = 0
for item in self . items :
total_quantity + = item . quantity
return total_quantity
2023-06-28 23:58:33 +00:00
class Character :
2023-07-11 07:26:52 +00:00
2023-07-15 22:59:50 +00:00
skill_ids = None
attribute_ids = None
trait_ids = None
condition_ids = None
@classmethod
def load_ids ( cls , skill_filename , attribute_filename , trait_filename , condition_filename ) :
if cls . skill_ids is None :
cls . skill_ids = { }
with open ( skill_filename , ' r ' ) as skill_file :
skill_reader = csv . reader ( skill_file , delimiter = ' \t ' )
for row in skill_reader :
cls . skill_ids [ int ( row [ 0 ] ) ] = row [ 1 ] . strip ( )
if cls . attribute_ids is None :
cls . attribute_ids = { }
with open ( attribute_filename , ' r ' ) as attribute_file :
attribute_reader = csv . reader ( attribute_file , delimiter = ' \t ' )
for row in attribute_reader :
cls . attribute_ids [ int ( row [ 0 ] ) ] = row [ 1 ] . strip ( )
if cls . trait_ids is None :
cls . trait_ids = { }
with open ( trait_filename , ' r ' ) as trait_file :
trait_reader = csv . reader ( trait_file , delimiter = ' \t ' )
for row in trait_reader :
cls . trait_ids [ int ( row [ 0 ] ) ] = row [ 1 ] . strip ( )
if cls . condition_ids is None :
cls . condition_ids = { }
with open ( condition_filename , ' r ' ) as condition_file :
condition_reader = csv . reader ( condition_file , delimiter = ' \t ' )
for row in condition_reader :
cls . condition_ids [ int ( row [ 0 ] ) ] = row [ 1 ] . strip ( )
@classmethod
def get_skill_from_code ( cls , code ) :
return cls . skill_ids . get ( code , None ) if cls . skill_ids is not None else None
@classmethod
def get_attribute_from_code ( cls , code ) :
return cls . attribute_ids . get ( code , None ) if cls . attribute_ids is not None else None
@classmethod
def get_trait_from_code ( cls , code ) :
return cls . trait_ids . get ( code , None ) if cls . trait_ids is not None else None
@classmethod
def get_condition_from_code ( cls , code ) :
return cls . condition_ids . get ( code , None ) if cls . condition_ids is not None else None
2023-06-30 02:37:25 +00:00
def __init__ ( self , name , tag ) :
2023-06-28 23:58:33 +00:00
self . name = name
2023-06-30 02:37:25 +00:00
self . tag = tag
2023-06-30 05:13:59 +00:00
self . is_clone = False
2023-06-28 23:58:33 +00:00
2023-06-30 04:59:23 +00:00
self . skills = [ ]
self . attributes = [ ]
2023-08-30 03:50:24 +00:00
self . conditions = [ ]
2023-06-28 23:58:33 +00:00
2023-06-30 02:37:25 +00:00
def set_skills ( self , skill_list ) :
2023-06-30 04:59:23 +00:00
# Expected format is a list of dictionaries
# Each dictionary has keys id, mxn, exp, expd, level
2023-06-30 02:37:25 +00:00
self . skills = skill_list
def set_attributes ( self , attribute_list ) :
2023-06-30 04:59:23 +00:00
# Expected format is a list of dictionaries
# Each dictionary has keys id, points
2023-06-30 02:37:25 +00:00
self . attributes = attribute_list
2023-06-28 23:58:33 +00:00
def maximize_skills ( self ) :
2023-06-30 04:59:23 +00:00
# Max LEVEL is 5
# Max MXN should be 8 for some reason
# Exp* keys are probably for a future experience system but I don't know how they work so I won't touch them
for skill in self . skills :
2023-08-28 23:14:30 +00:00
skill [ ' level ' ] = 7
2023-06-30 04:59:23 +00:00
skill [ ' mxn ' ] = 8
2023-06-28 23:58:33 +00:00
def maximize_attributes ( self ) :
2023-06-30 04:59:23 +00:00
# Max POINTS is 6
for attribute in self . attributes :
attribute [ ' points ' ] = 6
2023-08-30 13:56:35 +00:00
2023-08-30 03:50:24 +00:00
def clear_conditions ( self ) :
self . conditions = [ 1550 , 2246 , 3311 ]
2023-06-28 23:58:33 +00:00
2023-06-30 02:37:25 +00:00
def clone ( self , new_name ) :
2023-06-30 04:59:23 +00:00
# How to deep copy the skills/attributes appropriately?
2023-08-28 23:14:30 +00:00
print ( " Cloning character {} " . format ( self . name ) )
2023-06-30 05:13:59 +00:00
new_char = Character ( new_name , copy . copy ( self . tag ) )
new_char . is_clone = True
2023-06-30 06:50:42 +00:00
new_char . tag [ ' name ' ] = new_name
2023-06-30 05:13:59 +00:00
2023-07-15 23:39:00 +00:00
new_char . skills = [ s . copy ( ) for s in self . skills ]
new_char . attributes = [ a . copy ( ) for a in self . attributes ]
2023-06-30 05:13:59 +00:00
return new_char
2023-06-30 02:37:25 +00:00
2023-06-30 04:59:23 +00:00
def print_summary ( self ) :
2023-07-11 07:26:52 +00:00
console . print ( " Name: [bright_cyan] {} [/] " . format ( self . name ) )
console . print ( )
console . print ( " Skills: " )
for i , skill in enumerate ( self . skills ) :
2023-07-15 22:59:50 +00:00
row_string = " {:12} [bright_cyan] {:2} [/] " . format ( Character . get_skill_from_code ( skill [ ' id ' ] ) , skill [ ' level ' ] )
2023-07-11 07:26:52 +00:00
if i % 2 == 0 :
2023-07-18 23:31:27 +00:00
console . print ( " [on bright_black] {} [/] " . format ( row_string ) )
2023-07-11 07:26:52 +00:00
else :
console . print ( " {} " . format ( row_string ) )
console . print ( )
console . print ( " Attributes: " )
for i , attribute in enumerate ( self . attributes ) :
2023-07-15 22:59:50 +00:00
row_string = " {:12} [bright_cyan] {:2} [/] " . format ( Character . get_attribute_from_code ( attribute [ ' id ' ] ) , attribute [ ' points ' ] )
2023-07-11 07:26:52 +00:00
if i % 2 == 0 :
2023-07-18 23:31:27 +00:00
console . print ( " [on bright_black] {} [/] " . format ( row_string ) )
2023-07-11 07:26:52 +00:00
else :
console . print ( " {} " . format ( row_string ) )
2023-06-30 04:59:23 +00:00
2023-06-30 02:37:25 +00:00
def __repr__ ( self ) :
2023-06-30 04:59:23 +00:00
return " {} - Skills: {} - Attributes: {} " . format ( self . name , repr ( self . skills ) , repr ( self . attributes ) )
2023-06-30 02:37:25 +00:00
class Ship :
def __init__ ( self , name , owner , state , tag ) :
self . name = name
self . owner = owner
self . state = state
2023-07-11 07:26:52 +00:00
self . tag = tag # Soup tag corresponding to this ship's node
2023-06-30 02:37:25 +00:00
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 )
2023-06-30 05:48:37 +00:00
def add_item ( self , item ) :
2023-07-15 22:59:50 +00:00
normal_storage_areas = [ sa for sa in self . storage_areas if sa . is_normal_storage ]
2023-06-30 22:52:44 +00:00
min_storage_area = normal_storage_areas [ 0 ]
2023-06-30 05:48:37 +00:00
min_occupancy = min_storage_area . get_total_occupancy ( )
2023-06-30 22:52:44 +00:00
for storage_area in normal_storage_areas :
2023-06-30 05:48:37 +00:00
if storage_area . get_total_occupancy ( ) < min_occupancy :
min_storage_area = storage_area
min_occupancy = min_storage_area . get_total_occupancy ( )
min_storage_area . add_item ( item )
2023-08-28 23:14:30 +00:00
def redistribute_storage ( self ) :
normal_storage_areas = [ sa for sa in self . storage_areas if sa . is_normal_storage ]
total = 0
2023-08-31 08:16:33 +00:00
area_capacity = 0
2023-08-28 23:14:30 +00:00
for area in normal_storage_areas :
total + = area . get_total_occupancy ( )
2023-08-31 08:16:33 +00:00
area_capacity + = storage_ids [ area . type_id ] [ ' capacity ' ]
console . print ( " Total used: {} " . format ( total ) )
console . print ( " Total actual capacity: {} " . format ( area_capacity ) )
2023-08-28 23:14:30 +00:00
2023-08-31 21:23:53 +00:00
# Consolidate items
console . print ( " Consolidating items for {} " . format ( self . name ) )
unique_codes = [ ]
new_items = [ ]
for area in normal_storage_areas :
console . print ( " ----- New storage area " )
for item in area . items :
if item . code not in unique_codes :
unique_codes . append ( item . code )
console . print ( " Considering item with code {} " . format ( item . code ) )
old_item_found = False
for new_item in new_items :
if new_item . code == item . code :
new_item . quantity + = item . quantity
old_item_found = True
# console.print("Adding new item with code {}".format(item.code))
if old_item_found is False :
new_items . append ( item )
console . print ( " Total number of new items: {} " . format ( len ( new_items ) ) )
console . print ( " Total number of unique codes: {} " . format ( len ( unique_codes ) ) )
new_amount = 0
for item in new_items :
new_amount + = item . quantity
console . print ( new_amount )
for area in normal_storage_areas :
area . items = [ ]
for item in new_items :
console . print ( " {} : {} : {} " . format ( item . code , item . name , item . quantity ) )
min_occupancy = 2000
min_area = None
for area in normal_storage_areas :
if area . get_total_occupancy ( ) < min_occupancy :
min_area = area
min_area . items . append ( item )
item_number = 0
for area in normal_storage_areas :
for item in area . items :
item_number + = 1
console . print ( " {} : {} : {} " . format ( item . code , item . name , item . quantity ) )
console . print ( " Item number: {} " . format ( item_number ) )
2023-08-30 03:50:24 +00:00
def consolidate_weapons ( self ) :
normal_storage_areas = [ sa for sa in self . storage_areas if sa . is_normal_storage ]
for area in normal_storage_areas :
for item in area . items :
if item . code in weapon_ids :
console . print ( " {} : {} " . format ( item . code , item . quantity ) )
2023-06-30 02:37:25 +00:00
class Player :
2023-06-30 04:59:23 +00:00
def __init__ ( self , currency , tag ) :
2023-06-30 02:37:25 +00:00
self . currency = currency
2023-06-30 04:59:23 +00:00
self . tag = tag
2023-06-30 02:37:25 +00:00
class GameData :
2023-08-28 23:14:30 +00:00
def __init__ ( self , soup , item_database ) :
2023-06-30 02:37:25 +00:00
self . player = None
self . ships = [ ]
2023-08-30 03:50:24 +00:00
self . research = [ ]
2023-06-30 02:37:25 +00:00
self . soup = soup
2023-08-28 23:14:30 +00:00
self . item_database = item_database
2023-06-30 02:37:25 +00:00
def populate ( self ) :
# Step 1 - Player data
char_tag = self . soup . find ( ' playerBank ' )
2023-06-30 05:48:37 +00:00
currency = int ( char_tag [ ' ca ' ] )
2023-06-30 04:59:23 +00:00
self . player = Player ( currency , char_tag )
2023-06-30 02:37:25 +00:00
# Step 2 - Ship data
ship_tags = self . soup . find_all ( ' ship ' )
for ship_tag in ship_tags :
2023-06-30 20:32:58 +00:00
# Something strange is happening here
# In some unexplored ships/derelicts (?fog="true" in the tag) the name appears in the game but NOWHERE in the file
ship_name = " UNNAMED (?fogged) "
if ship_tag . has_attr ( ' sname ' ) :
ship_name = ship_tag [ ' sname ' ]
2023-07-15 23:39:00 +00:00
# console.print(ship_name)
2023-06-30 02:37:25 +00:00
2023-07-11 07:26:52 +00:00
# Forgotten - why did I specify owner as required here - what ships don't have owners?
2023-06-30 02:37:25 +00:00
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 ) :
2023-06-30 04:59:23 +00:00
storage_area = StorageArea ( inv_tag . find ( ' inv ' ) )
2023-07-11 07:26:52 +00:00
# 632 is the "m" id code for LARGE storage
storage_area . type_id = int ( inv_tag . parent . parent [ ' m ' ] )
2023-07-15 22:59:50 +00:00
storage_area . is_normal_storage = storage_area . type_id in normal_storage_ids
2023-06-30 02:37:25 +00:00
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 ' ] )
2023-07-15 22:59:50 +00:00
item_name = Item . get_name_from_code ( item_code )
2023-06-30 02:37:25 +00:00
item_quantity = int ( s_tag [ ' inStorage ' ] )
item = Item ( item_code , item_name , item_quantity )
storage_area . add_item ( item )
2023-07-15 23:39:00 +00:00
# console.print("{:4}: {} - {}".format(item_code, item_name, item_quantity))
2023-06-30 02:37:25 +00:00
# Step 4 - Character data
for character_list in ship_tag . find_all ( ' characters ' ) :
2023-07-11 07:26:52 +00:00
character_tags = character_list . find_all ( ' c ' , attrs = { ' name ' : True } )
2023-06-30 02:37:25 +00:00
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 = {
2023-07-11 07:26:52 +00:00
' id ' : int ( skill_id ) ,
' level ' : int ( skill_level ) ,
' mxn ' : int ( skill_mxn ) ,
' exp ' : int ( skill_exp ) ,
' expd ' : int ( skill_expd ) ,
2023-06-30 02:37:25 +00:00
}
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 = {
2023-07-11 07:26:52 +00:00
' id ' : int ( attribute_id ) ,
' points ' : int ( attribute_points ) ,
2023-06-30 02:37:25 +00:00
}
attributes . append ( attribute_dict )
character . set_attributes ( attributes )
2023-08-30 03:50:24 +00:00
# Step 5 - Research Data
# console.print('Finding research...')
res_tag = self . soup . find ( " research " , treeId = True )
item_ids = [ ]
for item in res_tag . find_all ( ' l ' , techId = True ) :
active_stage = int ( item [ ' activeStageIndex ' ] )
2023-08-30 13:56:35 +00:00
paused = item [ ' paused ' ]
2023-08-30 03:50:24 +00:00
techId = int ( item [ ' techId ' ] )
item_ids . append ( int ( item [ ' techId ' ] ) )
2023-08-30 13:56:35 +00:00
stages = [ ]
for stage in item . find_all ( ' l ' , done = True , stage = True ) :
done = stage [ ' done ' ]
stage_no = stage [ ' stage ' ]
# console.print(stage)
blocksdone_tag = stage . find ( ' blocksDone ' )
blocksdone = None
if blocksdone_tag is not None :
blocksdone = [ int ( blocksdone_tag [ ' level1 ' ] ) , int ( blocksdone_tag [ ' level2 ' ] ) , int ( blocksdone_tag [ ' level3 ' ] ) ]
ris = ResearchItemState ( done , stage_no , blocksdone )
stages . append ( ris )
2023-08-31 21:23:53 +00:00
ri = ResearchItem ( techId , active_stage , paused , stages , item )
2023-08-30 13:56:35 +00:00
self . research . append ( ri )
"""
# Research data
console . print ( ' Research stage counts: ' )
res_tag = self . soup . find ( ' research ' , treeId = True )
for category in res_tag . find_all ( ' l ' , techId = True ) :
console . print ( " {} : {} " . format ( category [ ' techId ' ] , len ( category . find_all ( ' l ' , done = True , stage = True ) ) ) )
"""
2023-08-30 03:50:24 +00:00
2023-06-30 02:37:25 +00:00
def writeback ( self ) :
2023-06-30 04:59:23 +00:00
def replace_id ( dict , old_key , new_key ) :
dict_copy = dict . copy ( )
dict_copy [ new_key ] = dict_copy [ old_key ]
del dict_copy [ old_key ]
return dict_copy
2023-06-30 05:13:59 +00:00
2023-06-30 02:37:25 +00:00
# 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)
2023-06-30 04:59:23 +00:00
# Shouldn't need to update player tag as this will be done directly
# Only need to update things with structure / lists
2023-07-11 07:26:52 +00:00
2023-06-30 04:59:23 +00:00
"""
2023-07-11 07:26:52 +00:00
Step 1 - Update characters
Step 2 - Update storage areas
2023-06-30 04:59:23 +00:00
"""
# Step 1 - Update characters
# Names etc will be done automatically - just need to reconstruct skills and attributes
2023-06-30 05:48:37 +00:00
self . player . tag [ ' ca ' ] = self . player . currency
2023-06-30 04:59:23 +00:00
for ship in self . ships :
2023-08-30 03:50:24 +00:00
# console.print('Doing chars...')
2023-06-30 04:59:23 +00:00
for character in ship . characters :
2023-06-30 05:13:59 +00:00
# Cloned character tags have to be added to the list
if character . is_clone :
2023-08-28 23:14:30 +00:00
print ( " ADDING CLONED CHARACTER " )
2023-06-30 05:13:59 +00:00
charlist_tag = ship . tag . find ( ' characters ' )
charlist_tag . append ( character . tag )
2023-06-30 04:59:23 +00:00
skill_tag = character . tag . find ( ' skills ' )
skill_tag . clear ( )
for skill in character . skills :
skill_copy = replace_id ( skill , ' id ' , ' sk ' )
new_tag = self . soup . new_tag ( ' s ' , attrs = skill_copy )
skill_tag . append ( new_tag )
attribute_tag = character . tag . find ( ' attr ' )
attribute_tag . clear ( )
for attribute in character . attributes :
new_tag = self . soup . new_tag ( ' a ' , attrs = attribute )
attribute_tag . append ( new_tag )
2023-08-30 03:50:24 +00:00
condition_tag = character . tag . find ( ' conditions ' )
condition_tag . clear ( )
for condition in character . conditions :
attrs = { " id " : str ( condition ) , " level " : " 1 " , " rs " : " 1 " }
new_tag = self . soup . new_tag ( ' c ' , attrs = attrs )
# What do the <rec> tags mean with ht and wt attrs?
# What does the ac attr mean on the m mood sub tag mean?
new_mood_tag = self . soup . new_tag ( ' mood ' )
new_submood_tag = self . soup . new_tag ( ' m ' , ac = " 5 " )
new_tag . append ( new_mood_tag )
new_mood_tag . append ( new_submood_tag )
condition_tag . append ( new_tag )
2023-06-30 04:59:23 +00:00
for storage_area in ship . storage_areas :
area_tag = storage_area . tag
area_tag . clear ( )
for item in storage_area . items :
2023-07-11 07:26:52 +00:00
tag_dict = { ' elementaryId ' : item . code , ' inStorage ' : item . quantity , ' onTheWayIn ' : 0 , ' onTheWayOut ' : 0 }
2023-06-30 04:59:23 +00:00
new_tag = self . soup . new_tag ( ' s ' , attrs = tag_dict )
area_tag . append ( new_tag )
2023-08-30 13:56:35 +00:00
for research_item in self . research :
pass
2023-06-30 05:48:37 +00:00
def add_item ( self , item_code , item_quantity ) :
item_name = self . item_database . get_name_from_code ( item_code )
item = Item ( item_code , item_name , item_quantity )
for ship in self . ships :
if ship . owner == " Player " :
ship . add_item ( item )
break
def buff_characters ( self ) :
for ship in self . ships :
if ship . owner == " Player " :
for character in ship . characters :
character . maximize_skills ( )
character . maximize_attributes ( )
2023-08-30 03:50:24 +00:00
character . clear_conditions ( )
2023-06-30 05:48:37 +00:00
def add_currency ( self , amount ) :
self . player . currency + = amount
2023-06-30 06:50:42 +00:00
def clone_character ( self , character_name , new_name ) :
2023-08-30 03:50:24 +00:00
console . print ( " Clone char method called " )
console . print ( " Warning names are case sensitive! " )
2023-06-30 06:50:42 +00:00
for ship in self . ships :
for character in ship . characters :
if character . name == character_name :
2023-08-28 23:14:30 +00:00
print ( " Found char to clone " )
2023-06-30 06:50:42 +00:00
new_char = character . clone ( new_name )
ship . characters . append ( new_char )
return
2023-06-30 06:09:15 +00:00
def print_detailed_character_summary ( self ) :
for ship in self . ships :
2023-07-11 07:26:52 +00:00
console . print ( " Listing characters for ship [magenta] {} [/] (owned by [bright_cyan] {} [/], state [bright_cyan] {} [/]): " . format ( ship . name , ship . owner , ship . state ) )
2023-06-30 06:37:37 +00:00
if len ( ship . characters ) == 0 :
2023-07-11 07:26:52 +00:00
console . print ( " This ship has no characters, skipping... " )
console . print ( )
console . print ( )
2023-06-30 06:37:37 +00:00
continue
2023-06-30 06:09:15 +00:00
for character in ship . characters :
2023-07-15 08:18:32 +00:00
console . print ( )
2023-06-30 06:09:15 +00:00
character . print_summary ( )
2023-07-15 08:18:32 +00:00
console . print ( )
2023-07-11 07:26:52 +00:00
console . print ( ' ----- ' )
2023-06-30 06:37:37 +00:00
2023-07-11 07:26:52 +00:00
console . print ( )
2023-06-30 06:37:37 +00:00
def print_detailed_item_summary ( self ) :
for ship in self . ships :
2023-07-11 07:26:52 +00:00
rich . print ( " Inventory for ship [#aa00ff] {} [/] (owned by [bright_cyan] {} [/], state [bright_cyan] {} [/]): " . format ( ship . name , ship . owner , ship . state ) )
2023-06-30 06:37:37 +00:00
if len ( ship . storage_areas ) == 0 :
2023-07-11 07:26:52 +00:00
console . print ( " This ship has no storage areas, skipping... " )
console . print ( )
console . print ( )
2023-06-30 06:37:37 +00:00
continue
for index , storage_area in enumerate ( ship . storage_areas ) :
2023-07-15 22:59:50 +00:00
console . print ( " Storage area [bright_cyan] {} [/] (type: [bright_cyan] {} [/], normal storage: [bright_cyan] {} [/]): " . format ( index , storage_ids [ storage_area . type_id ] [ " name " ] , storage_area . is_normal_storage ) )
2023-06-30 06:37:37 +00:00
if len ( storage_area . items ) == 0 :
2023-07-11 07:26:52 +00:00
console . print ( " This storage area is empty. " )
console . print ( )
2023-06-30 06:37:37 +00:00
continue
2023-07-11 07:26:52 +00:00
console . print ( " [bold] {:>4} {:30} {:>3} [/] " . format ( " ID " , " Name " , " # " ) )
total_quantity = 0
for i , item in enumerate ( storage_area . items ) :
2023-07-18 23:31:27 +00:00
row_string = " [bright_cyan] {:4} [/] {:30} [bright_cyan] {:3} [/] " . format ( item . code , item . name , item . quantity )
2023-07-11 07:26:52 +00:00
total_quantity + = item . quantity
if i % 2 == 0 :
2023-07-18 23:31:27 +00:00
console . print ( " [on bright_black] {} [/] " . format ( row_string ) )
2023-07-11 07:26:52 +00:00
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 :
2023-07-18 23:31:27 +00:00
console . print ( " [bright_red]WARNING: Storage capacity exceeded[/] " )
2023-07-11 07:26:52 +00:00
console . print ( )
console . print ( )
2023-06-30 06:09:15 +00:00
2023-06-30 02:37:25 +00:00
def print_summary ( self ) :
2023-07-15 23:39:00 +00:00
console . print ( " Start game summary: " )
console . print ( " Player currency: {} " . format ( self . player . currency ) )
console . print ( " Number of ships: {} " . format ( len ( self . ships ) ) )
2023-07-11 07:26:52 +00:00
2023-06-30 02:37:25 +00:00
for ship in self . ships :
2023-07-15 23:39:00 +00:00
console . print ( " {} (owner {} , state {} ) " . format ( ship . name , ship . owner , ship . state ) )
console . print ( " Contains {} storage area(s): " . format ( len ( ship . storage_areas ) ) )
2023-06-30 02:37:25 +00:00
for index , storage_area in enumerate ( ship . storage_areas ) :
2023-07-15 23:39:00 +00:00
console . print ( " Storage area {} - contains {} item(s) - occupancy {} unit(s) " . format ( index , len ( storage_area . items ) , storage_area . get_total_occupancy ( ) ) )
console . print ( " Has {} character(s): " . format ( len ( ship . characters ) ) )
2023-06-30 02:37:25 +00:00
for char in ship . characters :
2023-07-15 23:39:00 +00:00
console . print ( " {} " . format ( char . name ) )
2023-06-30 06:09:15 +00:00
# char.print_summary()
2023-06-30 02:37:25 +00:00
2023-08-28 23:14:30 +00:00
def redistribute ( self ) :
for ship in self . ships :
2023-08-31 21:23:53 +00:00
if ship . owner != ' Player ' or ship . state != ' Normal ' :
2023-08-31 08:16:33 +00:00
continue
2023-08-31 21:23:53 +00:00
console . print ( ship . name , ship . state )
2023-08-28 23:14:30 +00:00
ship . redistribute_storage ( )
2023-08-30 03:50:24 +00:00
def list_research ( self ) :
2023-08-31 11:51:13 +00:00
for research in sorted ( self . research , key = lambda item : item . id ) :
2023-08-30 03:50:24 +00:00
console . print ( research )
2023-08-30 13:56:35 +00:00
for ris in research . stage_states :
console . print ( ris )
2023-08-30 03:50:24 +00:00
def consolidate_weapons ( self ) :
for ship in self . ships :
if ship . owner == " Player " :
ship . consolidate_weapons ( )
2023-06-28 23:58:33 +00:00
2023-06-30 20:32:58 +00:00
def parse_item_file ( filename ) :
results = [ ]
for line in open ( filename ) :
line = line . strip ( )
if line [ 0 ] == ' # ' :
continue
components = line . split ( )
item_code = int ( components [ 0 ] )
item_quantity = int ( components [ 1 ] )
results . append ( ( item_code , item_quantity ) )
return results
2023-06-30 02:37:25 +00:00
2023-06-28 15:36:32 +00:00
def main ( ) :
2023-06-28 16:22:45 +00:00
parser = argparse . ArgumentParser ( prog = " Space Haven Saved Game Inspector " , description = " As above. " )
2023-06-30 22:52:44 +00:00
parser . add_argument ( ' filename ' , metavar = ' SAVEGAME_GAME_FILE ' )
2023-07-18 23:33:24 +00:00
parser . add_argument ( ' --add-item ' , required = False , metavar = ( ' ITEM_CODE ' , ' ITEM_QUANTITY ' ) , 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. " )
2023-06-30 22:52:44 +00:00
parser . add_argument ( ' --money ' , required = False , type = int , nargs = 1 , metavar = ' AMOUNT ' , help = " Give the player credits of the specified amount " )
2023-07-18 23:33:24 +00:00
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 " )
parser . add_argument ( ' --clone-character ' , required = False , type = str , nargs = 2 , metavar = ( ' OLD_NAME ' , ' NEW_NAME ' ) , help = " Clones a character of one name into another " )
parser . add_argument ( ' --add-item-set ' , required = False , type = str , nargs = 1 , metavar = ' PACK_FILENAME ' , help = " Takes a file containing a list of item codes and quantities (whitespace separated) and adds all of these to player storage " )
parser . add_argument ( ' --detailed-items ' , required = False , action = ' store_true ' , help = ' Print a detailed item listing from player inventory ' )
parser . add_argument ( ' --detailed-chars ' , required = False , action = ' store_true ' , help = ' Print a comprehensive listing of player character details ' )
2023-08-28 23:14:30 +00:00
parser . add_argument ( ' --redistribute ' , required = False , action = ' store_true ' , help = ' Redistribute internal storage ' )
2023-08-30 03:50:24 +00:00
parser . add_argument ( ' --list-research ' , required = False , action = ' store_true ' , help = ' List all research/research states ' )
parser . add_argument ( ' --consolidate-weapons ' , required = False , action = ' store_true ' , help = ' Put all weapons in one storage area ' )
2023-07-18 23:33:24 +00:00
parser . add_argument ( ' --replace-original ' , required = False , action = ' store_true ' , help = ' Replace original file instead of creating edited alternative. Renames original for backup, but USE WITH CAUTION ' )
2023-06-28 16:22:45 +00:00
args = parser . parse_args ( )
2023-07-15 23:39:00 +00:00
# console.print(args)
2023-06-28 23:58:33 +00:00
2023-07-15 23:39:00 +00:00
console . print ( " --- Space Haven Saved Game Inspector --- " )
console . print ( )
2023-06-28 16:22:45 +00:00
2023-08-28 23:14:30 +00:00
filename = os . path . join ( DEFAULT_SAVEGAMEPATH , args . filename , " save " , " game " )
2023-07-15 23:39:00 +00:00
# console.print(filename)
2023-06-28 15:36:32 +00:00
full_text = " "
for line in open ( filename ) :
full_text + = line
2023-07-15 23:39:00 +00:00
# console.print(full_text)
2023-06-28 15:36:32 +00:00
soup = BeautifulSoup ( full_text , " xml " )
2023-07-15 22:59:50 +00:00
Item . load_ids ( ' item_ids.tsv ' )
2023-07-15 23:39:00 +00:00
console . print ( " Item code database successfully loaded. " )
console . print ( )
2023-07-15 22:59:50 +00:00
Character . load_ids ( ' char_skills.tsv ' , ' char_attributes.tsv ' , ' char_traits.tsv ' , ' char_conditions.tsv ' )
2023-07-15 23:39:00 +00:00
console . print ( " Character code database successfully loaded. " )
console . print ( )
2023-08-30 03:50:24 +00:00
ResearchItem . load_ids ( ' research2.tsv ' )
console . print ( " Research name database successfully loaded. " )
console . print ( )
2023-06-30 06:58:40 +00:00
2023-08-28 23:14:30 +00:00
game_data = GameData ( soup , Item )
2023-06-30 06:58:40 +00:00
game_data . populate ( )
2023-06-30 20:54:15 +00:00
edits_made = False
2023-06-30 06:58:40 +00:00
if args . buff_chars :
2023-08-30 03:50:24 +00:00
console . print ( " Buffing chars... " )
2023-06-30 06:58:40 +00:00
game_data . buff_characters ( )
2023-06-30 20:54:15 +00:00
edits_made = True
2023-08-28 23:14:30 +00:00
print ( " Buffed chars " )
2023-06-30 06:58:40 +00:00
if args . add_item :
2023-08-30 03:50:24 +00:00
console . print ( " Adding item... " )
2023-06-30 06:58:40 +00:00
game_data . add_item ( args . add_item [ 0 ] , args . add_item [ 1 ] )
2023-06-30 20:54:15 +00:00
edits_made = True
2023-06-30 06:58:40 +00:00
if args . money :
2023-08-30 03:50:24 +00:00
console . print ( " Adding currency... " )
2023-06-30 06:58:40 +00:00
game_data . add_currency ( args . money [ 0 ] )
2023-06-30 20:54:15 +00:00
edits_made = True
2023-06-30 06:58:40 +00:00
if args . list_ships :
2023-08-30 03:50:24 +00:00
console . print ( " Listing ships... " )
2023-06-30 06:58:40 +00:00
game_data . print_summary ( )
2023-06-30 07:07:33 +00:00
if args . clone_character :
2023-08-30 03:50:24 +00:00
console . print ( " Char clone requested... " )
2023-06-30 07:07:33 +00:00
game_data . clone_character ( args . clone_character [ 0 ] , args . clone_character [ 1 ] )
2023-06-30 20:54:15 +00:00
edits_made = True
2023-06-30 07:07:33 +00:00
2023-06-30 20:32:58 +00:00
if args . add_item_set :
2023-08-30 03:50:24 +00:00
console . print ( " Adding item set... " )
2023-06-30 20:32:58 +00:00
item_list = parse_item_file ( args . add_item_set [ 0 ] )
2023-07-15 23:39:00 +00:00
# console.print(item_list)
console . print ( " Items to be added: " )
2023-06-30 20:32:58 +00:00
for ( item_code , item_quantity ) in item_list :
2023-07-15 23:39:00 +00:00
console . print ( " {} : {} " . format ( Item . get_name_from_code ( item_code ) , item_quantity ) )
2023-06-30 20:32:58 +00:00
game_data . add_item ( item_code , item_quantity )
2023-06-30 20:54:15 +00:00
edits_made = True
2023-06-30 06:58:40 +00:00
2023-06-30 20:32:58 +00:00
if args . detailed_items :
2023-08-30 03:50:24 +00:00
console . print ( " Printing detailed item list... " )
2023-06-30 20:32:58 +00:00
game_data . print_detailed_item_summary ( )
2023-06-28 23:58:33 +00:00
2023-06-30 20:32:58 +00:00
if args . detailed_chars :
2023-08-30 03:50:24 +00:00
console . print ( " Printing detailed character list... " )
2023-06-30 20:32:58 +00:00
game_data . print_detailed_character_summary ( )
2023-06-28 23:58:33 +00:00
2023-08-28 23:14:30 +00:00
if args . redistribute :
2023-08-30 03:50:24 +00:00
console . print ( " Item redistribution requested... " )
2023-08-28 23:14:30 +00:00
game_data . redistribute ( )
2023-08-30 03:50:24 +00:00
if args . list_research :
console . print ( " Listing research status... " )
game_data . list_research ( )
if args . consolidate_weapons :
console . print ( " Consolidating weapons... " )
game_data . consolidate_weapons ( )
2023-06-30 20:32:58 +00:00
game_data . writeback ( )
2023-06-28 15:36:32 +00:00
2023-06-30 02:37:25 +00:00
if args . test_gamedata :
2023-07-15 22:59:50 +00:00
game_data = GameData ( soup )
2023-06-30 02:37:25 +00:00
game_data . populate ( )
2023-06-30 06:37:37 +00:00
# game_data.add_item(1759, 100)
# game_data.buff_characters()
# game_data.add_currency(10000)
2023-06-30 02:37:25 +00:00
game_data . print_summary ( )
2023-06-30 20:32:58 +00:00
game_data . print_detailed_item_summary ( )
# game_data.clone_character("Byron", "Anthony")
# game_data.print_detailed_character_summary()
2023-06-30 04:59:23 +00:00
game_data . writeback ( )
2023-06-30 02:37:25 +00:00
2023-06-30 20:54:15 +00:00
if edits_made :
if args . replace_original :
2023-07-15 23:39:00 +00:00
console . print ( ' Renaming original file ' )
2023-08-31 21:23:53 +00:00
filename = os . path . join ( DEFAULT_SAVEGAMEPATH , args . filename , " save " , " game " )
2023-06-30 20:54:15 +00:00
datetime_suffix = datetime . datetime . now ( ) . strftime ( ' % Y- % m- %d - % H % M_ % S_ %f ' )
2023-08-31 21:23:53 +00:00
new_filename = os . path . join ( DEFAULT_SAVEGAMEPATH , args . filename , " save " , " game " + ' - ' + datetime_suffix )
2023-07-15 23:39:00 +00:00
# console.print(datetime_suffix)
2023-08-31 21:23:53 +00:00
os . rename ( filename , new_filename )
2023-06-30 20:54:15 +00:00
2023-07-15 23:39:00 +00:00
console . print ( ' Now rewriting game file with new information ' )
2023-06-30 20:54:15 +00:00
text = soup . prettify ( )
# Delete XML header - game doesn't have it
sansfirstline = ' \n ' . join ( text . split ( ' \n ' ) [ 1 : ] )
2023-08-28 23:14:30 +00:00
f = open ( filename , ' w ' )
2023-06-30 20:54:15 +00:00
f . write ( sansfirstline )
f . close ( )
2023-07-15 23:39:00 +00:00
console . print ( ' Complete. ' )
2023-06-30 20:54:15 +00:00
else :
text = soup . prettify ( )
# Delete XML header - game doesn't have it
sansfirstline = ' \n ' . join ( text . split ( ' \n ' ) [ 1 : ] )
f = open ( ' game.xml ' , ' w ' )
f . write ( sansfirstline )
f . close ( )
2023-06-28 15:36:32 +00:00
2023-07-11 07:26:52 +00:00
2023-06-28 15:36:32 +00:00
if __name__ == " __main__ " :
main ( )