from __future__ import annotations import os from constants import DLC_NAMES from loader import ( InitialPakLoader, LanguageLoader, SnowrunnerGameObjectType, TruckAddonCategory, GameData, SnowrunnerClass, ) from logging import getLogger logger = getLogger(__name__) def get_head(language: LanguageLoader, category: str, name: str | None = None) -> str: return f"""\n\n""" + ( f""" SnowRunnerDB - {category}""" + (f""" - {name}""" if name is not None else "") + f"""
""" ) def get_footer(): return f"""
\n\n """ def write_html( file_path: str | os.PathLike, language: LanguageLoader, content: str, category: str, name: str | None = None ): with open(file_path, "w") as f: f.write(get_head(language=language, category=category, name=name)) f.write(content) f.write(get_footer()) def write_trucks(pak: InitialPakLoader, destination_directory: str, language: LanguageLoader): index_content = "

Trucks

\n" index_content += f" \n" index_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) index_content += f" \n" for reference, truck in pak.get_sorted_object_list(SnowrunnerGameObjectType.TRUCK, language): if truck.Type != "Truck": continue truck_file = os.path.join(destination_directory, reference + ".html") truck_content = f"

{truck.get_translated_ui_name(language)}

" truck_content += f"
NameClassDLCUnlock byRegionPriceWeightDrivetrainAWDDiffLock
\n" try: truck_content += f" \n" except KeyError: print(f"DEBUG: DLC {truck.DLC} for Truck {truck.get_translated_ui_name(language)}") exit(1) truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f"
DLC{DLC_NAMES[truck.DLC]}
Weight{truck.Weight}kg
AWD{language[truck.AWDInfo.value]}
DiffLock{language[truck.DiffLockInfo.value]}
Def Engine{truck.TruckData.DefaultEngine.get_translated_ui_name(language)}
Def Gearbox{truck.TruckData.DefaultGearbox.get_translated_ui_name(language)}
Def Suspension{truck.TruckData.DefaultSuspension.get_translated_ui_name(language)}
Def Winch{truck.TruckData.DefaultWinch.get_translated_ui_name(language)}
Def Tire{truck.TruckData.Wheels.DefaultTire.get_translated_ui_name(language)}
\n" # Engines truck_content += f"

Engines

\n" engines = [x for y in truck.TruckData.Engines for x in y.Engine] truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) truck_content += f" \n" # Power2Weight # Power2Fuel for engine in sorted(engines, key=lambda x: x.Torque): truck_content += ( f" " f" " f" " f" " f" " f" " f" \n" ) truck_content += f"
NamePriceUnlock ByBase Fuel ConsumptionTorquePower-to-Weight
{engine.get_translated_ui_name(language)}{engine.GameData.Price}" f" {engine.GameData.get_unlock_str()}{engine.FuelConsumption}{engine.Torque}{engine.Torque / truck.Weight:.2f}
\n" # Gearboxes gearboxes = [y for x in truck.TruckData.Gearboxes for y in x.Gearbox] max_gear_count = max(len(x.Gear) for x in gearboxes) truck_content += f"

Gearboxes

\n" truck_content += f"

General

\n" truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) truck_content += f" \n" for gearbox in sorted(gearboxes, key=lambda x: x.get_translated_ui_name(language)): truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += ( f" \n" ) truck_content += ( f" \n" ) truck_content += ( f" \n" ) truck_content += ( f" \n" ) truck_content += ( f" \n" ) truck_content += f" \n" truck_content += f" \n" truck_content += f"
NamePriceUnlock ByFine-TunableL-LL+HGears
{gearbox.get_translated_ui_name(language)}{gearbox.Price}{gearbox.GameData.get_unlock_str()}{'✓' if gearbox.GearboxParams.IsManualLowGear else '˙'}{'✓' if gearbox.GearboxParams.IsLowerMinusGearExists else '˙'}{'✓' if gearbox.GearboxParams.IsLowerGearExists else '˙'}{'✓' if gearbox.GearboxParams.IsLowerPlusGearExists else '˙'}{'✓' if gearbox.GearboxParams.IsHighGearExists else '˙'}{len(gearbox.Gear)}
\n" truck_content += f"

Fuel Consumption

\n" truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" ) for i in range(max_gear_count): gear_num = str(i + 1) if i > 0 else "L+/1" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" for gearbox in sorted(gearboxes, key=lambda x: x.get_translated_ui_name(language)): truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" for i in range(max_gear_count): if i >= len(gearbox.Gear): truck_content += f" \n" else: truck_content += f" \n" truck_content += f" \n" truck_content += f"
NameFuel ConsumptionAWD Fuel Modifier{gear_num}
{gearbox.get_translated_ui_name(language)}{gearbox.FuelConsumption}{gearbox.AWDConsumptionModifier}-{gearbox.Gear[i].FuelModifier}
\n" # Suspensions truck_content += f"

Suspensions

\n" # Wheels truck_content += f"

Tires

\n" wheel_cat = {} scales = set() for cw in truck.TruckData.CompatibleWheels: scales.add(cw.get_size_in_inches()) damage_cap = cw.TruckWheels.DamageCapacity width = int(cw.TruckWheels.Width * cw.Scale * 100) mass = cw.TruckWheels.Mass # FIXME: cw.TruckWheels.GameData.ReadMassScale (affects the truck mass for dualies) for tt in cw.TruckWheels.TruckTires.TruckTire: if tt.Mass > 0: mass = tt.Mass if tt.Width > 0: width = int(tt.Width * cw.Scale * 100) cat = tt.WheelFriction.get_translated_ui_name(language) if cat not in wheel_cat: wheel_cat[cat] = {} wheel_cat[cat][tt.UiName] = (damage_cap, width, mass, tt) # Mud_Performance= # Truck_Weight / (Tire_Count×(Tire_Diameter×Width×Softness)) * Tire_Mud_Rating for cat in sorted(wheel_cat): truck_content += f"

{cat}

\n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" for damage_cap, width, mass, tt in sorted( wheel_cat[cat].values(), key=lambda v: v[3].get_translated_ui_name(language) ): truck_content += f" \n" truck_content += f"
 Friction
NamePriceMassWidthAsphaltOff-RoadMud
{tt.get_translated_ui_name(language)}{tt.GameData.Price}{mass}kg{width}cm{tt.WheelFriction.BodyFrictionAsphalt}{tt.WheelFriction.BodyFriction}{tt.WheelFriction.SubstanceFriction}
\n" scales = ", ".join(['%d"' % x for x in sorted(scales)]) truck_content += f"

Available Tire Sizes: {scales}

\n" # Winches truck_content += f"

Winches

\n" # Diff. Lock diff_lock_addons = truck.get_available_truck_addons(TruckAddonCategory.DIFF_LOCK) if diff_lock_addons: truck_content += f"

Diff. Lock

\n" truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" ) for addon in sorted(diff_lock_addons, key=lambda v: v.get_translated_ui_name(language)): truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" ) truck_content += f"
NamePriceUnlock By
{addon.get_translated_ui_name(language)}{addon.Price}{addon.GameData.get_unlock_str()}
\n" # Spare Wheel (if any) # Snorkel (if any) snorkel_addons = truck.get_available_truck_addons(TruckAddonCategory.SNORKEL) if snorkel_addons: truck_content += f"

Snorkel

\n" truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) for addon in sorted(snorkel_addons, key=lambda v: v.get_translated_ui_name(language)): truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) truck_content += f"
NamePriceUnlock ByMass
{addon.get_translated_ui_name(language)}{addon.Price}{addon.GameData.get_unlock_str()}{addon.PhysicsModel.Weight}kg
\n" # AWD awd_addons = truck.get_available_truck_addons(TruckAddonCategory.ALL_WHEEL_DRIVE) if awd_addons: truck_content += f"

AWD

\n" truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) for addon in sorted(awd_addons, key=lambda v: v.get_translated_ui_name(language)): truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) truck_content += f"
NamePriceUnlock ByMass
{addon.get_translated_ui_name(language)}{addon.Price}{addon.GameData.get_unlock_str()}{addon.PhysicsModel.Weight}kg
\n" # Frame Addons truck_content += f"

Frame Addons

\n" truck_content += f" \n" truck_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) for truck_addon in sorted( truck.get_available_truck_addons(TruckAddonCategory.FRAME_ADDON), key=lambda v: v.get_translated_ui_name(language), ): truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f" \n" truck_content += f"
NamePriceUnlock ByMassCargo SlotsRepair PointsSpare WheelsFuel CapacityWater Capacity
{truck_addon.get_translated_ui_name(language)}{truck_addon.Price}{truck_addon.GameData.get_unlock_str()}{truck_addon.PhysicsModel.Weight}kg{truck_addon.AddonSlots.Quantity if truck_addon.AddonSlots is not None else ' '}{truck_addon.TruckData.RepairsCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.RepairsCapacity > 0 else ' '}{truck_addon.TruckData.WheelRepairsCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.WheelRepairsCapacity > 0 else ' '}{truck_addon.TruckData.FuelCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.FuelCapacity > 0 else ' '}{truck_addon.TruckData.WaterCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.WaterCapacity > 0 else ' '}
\n" truck_content += f" \n" write_html( truck_file, language=language, content=truck_content, category="Trucks", name=truck.get_translated_ui_name(language), ) index_content += ( f" \n" f" {truck.get_translated_ui_name(language)}\n" f" {language.get_sanitized(truck.TruckData.TruckType)}\n" f" {DLC_NAMES[truck.DLC]}\n" f" {truck.GameData.get_unlock_str()}\n" f" {truck.GameData.Country}\n" f" {truck.get_shop_price()}\n" f" {truck.Weight}\n" f" {truck.DriveTrain}\n" f" {language[truck.AWDInfo.value]}\n" f" {language[truck.DiffLockInfo.value]}\n" f" \n" ) index_content += f" \n" index_content += " \n" write_html( os.path.join(destination_directory, "index.html"), language=language, content=index_content, category="Trucks" ) def write_frame_addons(pak: InitialPakLoader, destination_directory: str, language: LanguageLoader): index_content = "

Frame Addons

\n" index_content += f" \n" index_content += ( f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" f" \n" ) index_content += f" \n" for _, frame_addon in pak.get_sorted_object_list(SnowrunnerGameObjectType.TRUCK_ADDON, language=language): if frame_addon.GameData.Category != TruckAddonCategory.FRAME_ADDON: continue if ( "TRANSLATION NOT FOUND" in frame_addon.get_translated_ui_name(language) or len(frame_addon.get_translated_ui_name(language)) > 100 # This is a hidden frame addon where somebody swapped UI_NAME and UI_DESC ): continue # FIXME: How To better detect Hidden Frame Addons not shown in game? # Also e.g. Mack Defense has a Spare Wheel Addon that's always installed and not shown in game?! available_trucks = [ x for x in pak.get_sorted_object_list(SnowrunnerGameObjectType.TRUCK, language=language) if x[1].Type == "Truck" and frame_addon.GameData.InstallSocket in x[1].get_addon_socket_names() ] if not available_trucks: continue index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f" \n" truck_links = ", ".join( f'{truck.get_translated_ui_name(language)}' for ref, truck in available_trucks ) index_content += f" \n" index_content += f" \n" index_content += f" \n" index_content += f"
NamePriceUnlock ByMassCargo SlotsRepair PointsSpare WheelsFuel CapacityWater CapacityAvailable for
{frame_addon.get_translated_ui_name(language)}{frame_addon.Price}{frame_addon.GameData.get_unlock_str()}{frame_addon.PhysicsModel.Weight}kg{frame_addon.AddonSlots.Quantity if frame_addon.AddonSlots is not None else ' '}{frame_addon.TruckData.RepairsCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.RepairsCapacity > 0 else ' '}{frame_addon.TruckData.WheelRepairsCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.WheelRepairsCapacity > 0 else ' '}{frame_addon.TruckData.FuelCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.FuelCapacity > 0 else ' '}{frame_addon.TruckData.WaterCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.WaterCapacity > 0 else ' '}{truck_links}
\n" index_content += " \n" write_html( os.path.join(destination_directory, "index.html"), language=language, content=index_content, category="Frame Addons", ) def create_html(pak: InitialPakLoader, destination_directory: str = "output/"): if not os.path.exists(destination_directory): os.mkdir(destination_directory) logger.info("Writing HTML Output") for language_name, language_data in pak.languages.items(): logger.info(f" - {language_name}") language_path = os.path.join(destination_directory, language_name) if not os.path.exists(language_path): os.mkdir(language_path) truck_path = os.path.join(destination_directory, language_name, "trucks") if not os.path.exists(truck_path): os.mkdir(truck_path) write_trucks(pak=pak, destination_directory=truck_path, language=language_data) frame_addon_path = os.path.join(destination_directory, language_name, "frame_addons") if not os.path.exists(frame_addon_path): os.mkdir(frame_addon_path) write_frame_addons(pak=pak, destination_directory=frame_addon_path, language=language_data)