diff --git a/constants.py b/constants.py
new file mode 100644
index 0000000..779e682
--- /dev/null
+++ b/constants.py
@@ -0,0 +1,64 @@
+COUNTRYS = {}
+
+DLC_NAMES = {
+ None: "Base Game",
+ "dlc_1_1": "Season 1: Search & Recover Kola Peninsula, Russia",
+ "dlc_1_2": "Clasico Pack",
+ "dlc_2_1": "Season 2: Explore & Expand Yukon, Canada",
+ "dlc_2_2": "Season 2: Explore & Expand",
+ "dlc_3": "Season 3: Locate & Deliver Wisconsin, USA",
+ "DLC_trial_6": "GMC Brigadier",
+ "dlc_4": "Season 4: New Frontiers Amur, Russia",
+ "dlc_4_1": "Anniversary DLC",
+ "dlc_5": "Season 5: Build & Dispatch Don, Russia",
+ "dlc_6": "Season 6: Haul & Hustle Maine, USA",
+ "dlc_7": "Season 7: Compete & Conquer Tennessee, USA",
+ "dlc_8": "Season 8: Grand Harvest Belozersk Glades, Central Asia",
+ "dlc_9": "Season 9: Renew & Rebuild Ontario, Canada",
+ "dlc_10": "Season 10: Fix & Connect British Columbia, Canada",
+ "dlc_11": "Season 11: Light & Cameras Scandinavia, Northern Europe",
+ "dlc_12": "Season 12: Public Energy North Carolina, USA",
+ "dlc_12_5": "The Atom",
+ "dlc_13": "Season 13: Dig & Drill Almaty Region, Central Asia",
+ "dlc_13_5": "Rezvani Tank",
+ "dlc_14": "Season 14: Reap & Sow Austria, Central Europe",
+ "dlc_14_1": "Kenworth Dual Pack",
+ "dlc_14_2": "Mack Dual Pack",
+ "dlc_15": "Season 15: Oil & Dirt Quebec, Canada",
+ "dlc_15_5": "Freightliner & Western Star Dual Pack",
+ "dlc_16": "Season 16: High Voltage Washington, USA",
+ "dlc_16_5": "Dragon Wagons Dual Pack",
+ "dlc_17": "Season 17: Repair & Rescue Zurdania, Western Asia",
+ "dlc_17_5": "Mercedes-Benz Trucks Dual Pack 1",
+ "dlc_18": "Season 18: Patch & Power Chiapas, Mexico",
+ "dlc_navistar": "Navistart 5000-MV Tractor",
+ "dlc_high_roller": "High Roller Pack",
+ "dlc_ws_49x": "Western Star 49X",
+ "dlc_tatra": "TATRA Dual Pack",
+ "dlc_jeep": "Jeep Dual Pack",
+ "dlc_crocodile": "Crocodile Pack",
+ "dlc_land_rover": "Land Rover Dual Pack",
+ "dlc_ws_wolf": "Western Star Wolf Pack",
+ "dlc_rezvani": "Rezvani Hercules",
+ "dlc_mastodon": "The Mastodon",
+ "dlc_w990": "Kenworth W990",
+}
+
+DLC_OVERRIDES = {
+ "UI_VEHICLE_NAVISTAR_5000_NAME": "dlc_navistar",
+ "UI_VEHICLE_KHAN_39_MARSHALL_NAME": "dlc_high_roller",
+ "UI_VEHICLE_WESTERN_STAR_49X_NAME": "dlc_ws_49x",
+ "UI_VEHICLE_TATRA_805_NAME": "dlc_tatra",
+ "UI_VEHICLE_TATRA_T813_NAME": "dlc_tatra",
+ "UI_VEHICLE_JEEP_CJ7_RENEGADE_NAME": "dlc_jeep",
+ "UI_VEHICLE_JEEP_WRANGLER_NAME": "dlc_jeep",
+ "UI_VEHICLE_STEP_3364_CROCODILE_NAME": "dlc_crocodile",
+ "UI_VEHICLE_LAND_ROVER_DEFENDER_110_NAME": "dlc_land_rover",
+ "UI_VEHICLE_LAND_ROVER_DEFENDER_90_NAME": "dlc_land_rover",
+ "UI_VEHICLE_WESTERN_STAR_57X_NAME": "dlc_ws_wolf",
+ "UI_VEHICLE_WESTERN_STAR_NF1424_NAME": "dlc_ws_wolf",
+ "UI_VEHICLE_WESTERN_STAR_NF1430_NAME": "dlc_ws_wolf",
+ "UI_VEHICLE_REZVANI_NAME": "dlc_rezvani",
+ "UI_VEHICLE_ZIKZ_612H_MASTODONT_NAME": "dlc_mastodon",
+ "UI_VEHICLE_KENWORTH_W990_NAME": "dlc_w990",
+}
diff --git a/loader.py b/loader.py
index 959c0e2..1431a69 100644
--- a/loader.py
+++ b/loader.py
@@ -1,4 +1,5 @@
from __future__ import annotations
+from constants import DLC_OVERRIDES
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum
from html import parser as html_parser
@@ -58,6 +59,37 @@ locatestarttagend_tolerant = re.compile(
endtagfind = re.compile(r"\s*([a-zA-Z_][-.a-zA-Z0-9:_]*)\s*>")
+class TruckInfo(str, Enum):
+ ALWAYS = "UI_TRUCK_INFO_ALWAYS"
+ CAPABLE = "UI_TRUCK_INFO_CAPABLE"
+ NOT_AVAILABLE = "UI_TRUCK_INFO_NA"
+ SWITCHABLE = "UI_TRUCK_INFO_ON_OFF"
+
+
+class TruckAddonCategory(str, Enum):
+ ALL_WHEEL_DRIVE = "awd"
+ BUBBLE_HEAD = "bubble_head"
+ BUMPER = "bumper"
+ CURTAIN = "curtain"
+ DIFF_LOCK = "diff_lock"
+ EXHAUST = "exhaust"
+ FRAME_ADDON = "frame_addons"
+ FENDERS = "fenders"
+ FENDER_FRONT = "fender_front"
+ FENDER_REAR = "fender_rear"
+ GRILL = "grill"
+ HOOD_ORNAMENT = "hood_ornament"
+ MIRROR_MOUNT = "mirror_mount"
+ MISCELLANEOUS = "miscellenious"
+ REAR = "rear"
+ SNORKEL = "snorkel"
+ STICKER = "sticker"
+ STICKER_EXTERIOR = "sticker_exterior"
+ STICKER_WINDSHIELD = "sticker_windshield"
+ TOP = "top"
+ WHEEL_ADDON = "wheel_addon"
+
+
def get_initial_pak_path():
return os.path.join(
str(os.path.expanduser("~")),
@@ -157,11 +189,12 @@ class SnowrunnerClass:
self.pak = pak_loader
self.UiDesc = ""
self.UiName = ""
- self.Price = -1
+ self.Price = 0
self.UnlockByExploration = False
self.UnlockByObjective = False
self.UnlockByRank = -1
self.GameData = None
+ self.DLC = None
def load_desc(self, data: SnowRunnerXNLNode):
if "region:default" in data.children:
@@ -174,7 +207,10 @@ class SnowrunnerClass:
self.UiName = data.get_attr("uiname")
def load_game_data(self, data: SnowRunnerXNLNode):
- self.GameData = self.pak.load_class(SnowrunnerGameObjectType.GAME_DATA, data)
+ if self.GameData is None:
+ self.GameData = self.pak.load_class(SnowrunnerGameObjectType.GAME_DATA, data, dlc_name=self.DLC)
+ else:
+ self.GameData.update_data(data)
self.Price = self.GameData.Price
self.UnlockByExploration = self.GameData.UnlockByExploration
self.UnlockByObjective = self.GameData.UnlockByObjective
@@ -201,7 +237,11 @@ class SnowrunnerClass:
self.__children_handled.add(name.lower())
def load_child(
- self, data: SnowRunnerXNLNode, name: str, object_type_override: SnowrunnerGameObjectType | None = None
+ self,
+ data: SnowRunnerXNLNode,
+ name: str,
+ object_type_override: SnowrunnerGameObjectType | None = None,
+ always_as_list: bool = False,
):
self.__children_handled.add(name.lower())
@@ -216,11 +256,22 @@ class SnowrunnerClass:
self.load_game_data(data.children[name.lower()])
return
+ if always_as_list and not isinstance(data.children[name.lower()], list):
+ data.children[name.lower()] = [data.children[name.lower()]]
+
object_type = SnowrunnerGameObjectType(name.lower()) if object_type_override is None else object_type_override
if isinstance(data.children[name.lower()], list):
- setattr(self, name, [self.pak.load_class(object_type, x) for x in data.children[name.lower()]])
+ setattr(
+ self,
+ name,
+ [self.pak.load_class(object_type, x, dlc_name=self.DLC) for x in data.children[name.lower()]],
+ )
else:
- setattr(self, name, self.pak.load_class(object_type, data.children[name.lower()]))
+ setattr(
+ self,
+ name,
+ self.pak.load_class(object_type, data.children[name.lower()], dlc_name=self.DLC),
+ )
def loading_finished(self, data: SnowRunnerXNLNode):
attr_set = set(data.attrs.keys())
@@ -239,6 +290,9 @@ class SnowrunnerClass:
)
sys.exit(1)
+ if self.UiName in DLC_OVERRIDES:
+ self.DLC = DLC_OVERRIDES[self.UiName]
+
def get_translated_ui_desc(self, language: LanguageLoader) -> str:
if self.UiDesc in language:
return language[self.UiDesc]
@@ -305,22 +359,30 @@ class AddonSlots(SnowrunnerClass):
super().__init__(pak_loader=pak_loader)
self.InitialOffset = "(0;0;0)"
self.Quantity = 0
+ self.UseTrailerFrame = False
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "InitialOffset")
self.load_attr(data, "Quantity")
+ self.load_attr(data, "UseTrailerFrame")
+ self.ignore_attr("OffsetStep")
+ self.ignore_attr("ParentFrames")
self.loading_finished(data)
class AddonSockets(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader)
- self.DefaultAddon = ""
+ self.DefaultAddon = None
self.Socket = None
def load_data(self, data: SnowRunnerXNLNode) -> None:
- self.load_attr(data, "DefaultAddon")
- self.load_child(data, "Socket")
+ if "defaultaddon" in data.attrs:
+ self.DefaultAddon = self.pak.get_object(
+ object_type=SnowrunnerGameObjectType.TRUCK_ADDON, name=data.attrs["defaultaddon"], lazy=True
+ )
+ self.ignore_attr("defaultaddon")
+ self.load_child(data, "Socket", always_as_list=True)
self.ignore_attr("ParentFrame")
self.ignore_attr("RequiredAddonIfNoConflicts")
self.loading_finished(data)
@@ -364,6 +426,10 @@ class Body(SnowrunnerClass):
self.ModelFrame = None
self.Body = []
+ @property
+ def Weight(self) -> int:
+ return self.Mass + sum(x.Weight for x in self.Body)
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "AllowedPenetrationDepth")
self.load_attr(data, "NoSoftContacts")
@@ -397,8 +463,7 @@ class Body(SnowrunnerClass):
self.ignore_attr("ExplicitParentFrame")
self.ignore_attr("NetSync")
self.ignore_attr("ParentFrame")
-
- self.load_child(data, "Body")
+ self.load_child(data, "Body", always_as_list=True)
self.ignore_child("Constraint")
self.ignore_child("Motor")
self.ignore_child("SideMirror")
@@ -489,18 +554,17 @@ class Engine(SnowrunnerClass):
class EngineVariants(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader)
- self.Engines = []
+ self.Engine = []
+
+ def get_engine(self, name: str) -> Engine | None:
+ for e in self.Engine:
+ if e.Name == name:
+ return e
+
+ return None
def load_data(self, data: SnowRunnerXNLNode) -> None:
- if "engine" in data.children:
- if isinstance(data.children["engine"], list):
- self.Engines = [
- self.pak.load_class(SnowrunnerGameObjectType.ENGINE, x) for x in data.children["engine"]
- ]
- else:
- self.Engines = [self.pak.load_class(SnowrunnerGameObjectType.ENGINE, data.children["engine"])]
- self.ignore_child("Engine")
-
+ self.load_child(data, "Engine", always_as_list=True)
self.loading_finished(data)
@@ -523,10 +587,11 @@ class GameData(SnowrunnerClass):
self.Country = ""
self.ExcludeAddons = ""
self.ExcludedCargoTypes = ""
+ self.InstallSocket = None
self.IsCustomizable = False
self.IsDoubleTrailer = False
self.IsQuest = False
- self.Price = -1
+ self.Price = 0
self.UnlockByRank = -1
self.UnlockByExploration = False
self.UnlockByObjective = False
@@ -544,7 +609,9 @@ class GameData(SnowrunnerClass):
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "CameraPreset")
- self.load_attr(data, "Category")
+ if "category" in data.attrs:
+ self.Category = TruckAddonCategory(data.attrs["category"])
+ self.ignore_attr("Category")
self.load_attr(data, "Country")
self.load_attr(data, "ExcludeAddons")
self.load_attr(data, "ExcludedCargoTypes")
@@ -577,8 +644,12 @@ class GameData(SnowrunnerClass):
self.ignore_attr("ResetTruckAfterGateway")
self.children = data.children
- self.load_child(data, "AddonSockets")
+ self.load_child(data, "AddonSockets", always_as_list=True)
self.load_child(data, "UiDesc")
+ if "installsocket" in data.children:
+ self.InstallSocket = data.children["installsocket"].attrs.get("type", self.InstallSocket)
+ self.ignore_child("InstallSocket")
+
self.ignore_child("AddonSlots")
self.ignore_child("AddonType")
self.ignore_child("CenteringAngularVelocity")
@@ -586,7 +657,6 @@ class GameData(SnowrunnerClass):
self.ignore_child("CustomizationCameras")
self.ignore_child("GearboxParams")
self.ignore_child("InstallSlot")
- self.ignore_child("InstallSocket")
self.ignore_child("LoadArea")
self.ignore_child("LongLogsAlignTarget")
self.ignore_child("RequiredAddon")
@@ -617,6 +687,9 @@ class GameData(SnowrunnerClass):
self.ignore_child("longlogsaligndata")
self.loading_finished(data)
+ def update_data(self, data: SnowRunnerXNLNode) -> None:
+ self.load_data(data)
+
class Gear(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader):
@@ -645,7 +718,7 @@ class Gearbox(SnowrunnerClass):
self.GearboxParams = None
self.ReverseGear = None
self.HighGear = None
- self.Gears = []
+ self.Gear = []
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "AWDConsumptionModifier")
@@ -660,10 +733,12 @@ class Gearbox(SnowrunnerClass):
self.load_child(data, "GameData")
if "gamedata" in data.children:
self.load_child(data.children["gamedata"], "GearboxParams")
- self.load_child(data, "Gear")
+ self.load_child(data, "Gear", always_as_list=True)
self.load_child(data, "ReverseGear", object_type_override=SnowrunnerGameObjectType.GEAR)
self.load_child(data, "HighGear", object_type_override=SnowrunnerGameObjectType.GEAR)
+ self.Gear = list(sorted(self.Gear, key=lambda gear: gear.AngVel))
+
self.loading_finished(data)
@@ -690,8 +765,15 @@ class GearboxVariants(SnowrunnerClass):
super().__init__(pak_loader=pak_loader)
self.Gearbox = []
+ def get_gearbox(self, name: str) -> Gearbox | None:
+ for e in self.Gearbox:
+ if e.Name == name:
+ return e
+
+ return None
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
- self.load_child(data, "Gearbox")
+ self.load_child(data, "Gearbox", always_as_list=True)
self.loading_finished(data)
def update_data(self, data: SnowRunnerXNLNode) -> None:
@@ -700,7 +782,7 @@ class GearboxVariants(SnowrunnerClass):
for orig, upd in zip_longest(self.Gearbox, data.children["gearbox"]):
if orig is None:
- self.Gearbox.append(self.pak.load_class(SnowrunnerGameObjectType.GEARBOX, upd))
+ self.Gearbox.append(self.pak.load_class(SnowrunnerGameObjectType.GEARBOX, upd, dlc_name=self.DLC))
elif upd is None:
return
else:
@@ -766,6 +848,15 @@ class PhysicsModel(SnowrunnerClass):
self.Mesh = ""
self.Body = None
+ @property
+ def Weight(self) -> int:
+ if not self.Body:
+ return 0
+ if isinstance(self.Body, list):
+ return sum(x.Weight for x in self.Body)
+ else:
+ return self.Body.Weight
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "_noinherit")
self.load_attr(data, "Mesh")
@@ -871,8 +962,15 @@ class SuspensionSetVariants(SnowrunnerClass):
super().__init__(pak_loader=pak_loader)
self.SuspensionSet = []
+ def get_suspension_set(self, name: str) -> SuspensionSet | None:
+ for e in self.SuspensionSet:
+ if e.Name == name:
+ return e
+
+ return None
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
- self.load_child(data, "SuspensionSet")
+ self.load_child(data, "SuspensionSet", always_as_list=True)
self.loading_finished(data)
@@ -891,6 +989,98 @@ class Truck(SnowrunnerClass):
self.Type = "Truck"
self.WaterMass = None
+ @property
+ def TireCount(self) -> int:
+ return len(self.TruckData.Wheels.Wheel)
+
+ @property
+ def TireCountFront(self) -> int:
+ return len([x for x in self.TruckData.Wheels.Wheel if x.Location == "front"])
+
+ @property
+ def TireCountRear(self) -> int:
+ return len([x for x in self.TruckData.Wheels.Wheel if x.Location == "rear"])
+
+ @property
+ def DriveTrain(self) -> str:
+ undriven_axles = len([x for x in self.TruckData.Wheels.Wheel if x.Torque == "none"])
+ return f"{self.TireCount}x{self.TireCount - undriven_axles}"
+
+ @property
+ def Weight(self) -> int:
+ wheel_mass = (
+ self.TruckData.Wheels.DefaultTire.Mass * self.TireCountFront
+ + self.TruckData.Wheels.DefaultTire.Mass
+ * self.TruckData.Wheels.DefaultTire.RearMassScale
+ * self.TireCountRear
+ )
+ default_addon_mass = sum(x.PhysicsModel.Weight for x in self.get_default_truck_addons())
+
+ return int(self.PhysicsModel.Weight + wheel_mass + default_addon_mass)
+
+ def get_shop_price(self) -> int:
+ return (
+ self.Price
+ + self.TruckData.DefaultEngine.Price
+ + self.TruckData.DefaultGearbox.Price
+ + self.TruckData.DefaultSuspension.Price
+ + self.TruckData.DefaultWinch.Price
+ + self.TruckData.Wheels.DefaultTire.Price
+ + sum(x.DefaultAddon.Price for x in self.GameData.AddonSockets if x.DefaultAddon is not None)
+ )
+
+ def get_addon_socket_names(self) -> list[str]:
+ return [s.Names for s in [y for x in self.GameData.AddonSockets for y in x.Socket]]
+
+ def get_available_truck_addons(self, category: TruckAddonCategory) -> list[TruckAddon]:
+ return [
+ ta
+ for ta in self.pak.get_object_list(SnowrunnerGameObjectType.TRUCK_ADDON)
+ if ta.GameData.InstallSocket in self.get_addon_socket_names() and ta.GameData.Category == category
+ ]
+
+ def get_default_truck_addons(self) -> list[TruckAddon]:
+ return [x.DefaultAddon for x in self.GameData.AddonSockets if x.DefaultAddon is not None]
+
+ @property
+ def AWDInfo(self) -> TruckInfo:
+ torques = {x.Torque.lower() for x in self.TruckData.Wheels.Wheel}
+ if len(torques) == 1 and "default" in torques:
+ return TruckInfo.ALWAYS
+ elif "connectable" in torques:
+ return TruckInfo.CAPABLE
+ elif "full" in torques:
+ return TruckInfo.SWITCHABLE
+ elif "none" in torques:
+ return TruckInfo.NOT_AVAILABLE
+ else:
+ raise ValueError(f"Truck {self.UiName} AWD Wheel Torques {torques} is not supported")
+
+ @property
+ def DiffLockInfo(self) -> TruckInfo:
+ if self.TruckData.DiffLockType == "Always":
+ return TruckInfo.ALWAYS
+ elif (
+ self.TruckData.DiffLockType == "Installed"
+ or self.TruckData.DiffLockType == "Switchable"
+ or self.TruckData.DiffLockType == "Connected"
+ or self.TruckData.DiffLockType == "Uninstalled"
+ or self.TruckData.DiffLockType == "None"
+ ): # DiffLockType seems to have no meaning. At all. We have to figure it out ourselfs.
+ diff_lock_addons = self.get_available_truck_addons(TruckAddonCategory.DIFF_LOCK)
+ if len(diff_lock_addons) == 0:
+ return TruckInfo.NOT_AVAILABLE
+ elif len(diff_lock_addons) == 1:
+ return TruckInfo.SWITCHABLE
+ else:
+ installed_addon = list(set(diff_lock_addons) & set(self.get_default_truck_addons()))
+ if installed_addon[0].TruckData.DiffLockInstalled:
+ return TruckInfo.SWITCHABLE
+ else:
+ return TruckInfo.CAPABLE
+ else:
+ raise ValueError(f"Truck {self.UiName} DiffLockType {self.TruckData.DiffLockType} is not supported")
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "AttachType")
self.load_attr(data, "IsDeprecated")
@@ -944,6 +1134,7 @@ class TruckAddon(SnowrunnerClass):
self.PhysicsModel = None
self.TruckData = None
self.AddonType = None
+ self.AddonSlots = None
self.RequiredAddonType = None
self.IsChassisFullOcclusion = False
self.FuelMass = None
@@ -966,7 +1157,7 @@ class TruckAddon(SnowrunnerClass):
if "installsocket" in data.children["gamedata"].children:
self.load_child(data.children["gamedata"], "InstallSocket")
if "addonslots" in data.children["gamedata"].children:
- self.load_child(data.children["gamedata"], "AddonType")
+ self.load_child(data.children["gamedata"], "AddonSlots")
if "addontype" in data.children["gamedata"].children:
self.load_child(data.children["gamedata"], "AddonType")
if "requiredaddon" in data.children["gamedata"].children:
@@ -1007,12 +1198,25 @@ class TruckAddon(SnowrunnerClass):
class TruckData(SnowrunnerClass):
+ truck_types = {
+ "HEAVY": "CODEX_TYPE_TRUCK_6_HEADER",
+ "HEAVY_DUTY": "CODEX_TYPE_TRUCK_4_HEADER",
+ "SCOUT": "CODEX_TYPE_TRUCK_3_HEADER",
+ "HIGHWAY": "CODEX_TYPE_TRUCK_2_HEADER",
+ "OFFROAD": "CODEX_TYPE_TRUCK_5_HEADER",
+ }
+
def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader)
self.AllWheelDriveInstalled = False
self.BackSteerSpeed = 0.0
self.CompatibleWheels = []
self.Damage = None
+ self.DefaultEngine = None
+ self.DefaultGearbox = None
+ self.DefaultSuspension = None
+ self.DefaultTire = None
+ self.DefaultWinch = None
self.DiffLockInstalled = False
self.DiffLockType = ""
self.Engines = []
@@ -1041,12 +1245,12 @@ class TruckData(SnowrunnerClass):
self.load_attr(data, "Responsiveness")
self.load_attr(data, "SetHandbrakeOnWinchRelease")
self.load_attr(data, "SteerSpeed")
- self.load_attr(data, "TruckType")
+ if "trucktype" in data.attrs:
+ self.TruckType = self.truck_types[data.attrs["trucktype"]]
+ self.ignore_attr("trucktype")
self.load_attr(data, "WaterCapacity")
self.load_attr(data, "WheelRepairsCapacity")
- self.load_child(data, "CompatibleWheels")
- if self.CompatibleWheels and not isinstance(self.CompatibleWheels, list):
- self.CompatibleWheels = [self.CompatibleWheels]
+ self.load_child(data, "CompatibleWheels", always_as_list=True)
self.load_child(data, "Damage")
self.load_child(data, "Wheels")
@@ -1058,6 +1262,9 @@ class TruckData(SnowrunnerClass):
self.pak.get_object(object_type=SnowrunnerGameObjectType.ENGINE_VARIANTS, name=n.strip())
for n in data.children["enginesocket"].attrs["type"].split(",")
]
+ self.DefaultEngine = list(
+ {x.get_engine(data.children["enginesocket"].attrs["default"]) for x in self.Engines} - {None}
+ )[0]
self.ignore_child("EngineSocket")
if "gearboxsocket" in data.children:
@@ -1065,6 +1272,9 @@ class TruckData(SnowrunnerClass):
self.pak.get_object(object_type=SnowrunnerGameObjectType.GEARBOX_VARIANTS, name=n.strip())
for n in data.children["gearboxsocket"].attrs["type"].split(",")
]
+ self.DefaultGearbox = list(
+ {x.get_gearbox(data.children["gearboxsocket"].attrs["default"]) for x in self.Gearboxes} - {None}
+ )[0]
self.ignore_child("GearboxSocket")
if "suspensionsocket" in data.children:
@@ -1072,6 +1282,10 @@ class TruckData(SnowrunnerClass):
self.pak.get_object(object_type=SnowrunnerGameObjectType.SUSPENSION_SET_VARIANTS, name=n.strip())
for n in data.children["suspensionsocket"].attrs["type"].split(",")
]
+ self.DefaultSuspension = list(
+ {x.get_suspension_set(data.children["suspensionsocket"].attrs["default"]) for x in self.Suspensions}
+ - {None}
+ )[0]
self.ignore_child("SuspensionSocket")
if "winchupgradesocket" in data.children:
@@ -1079,6 +1293,9 @@ class TruckData(SnowrunnerClass):
self.pak.get_object(object_type=SnowrunnerGameObjectType.WINCH_VARIANTS, name=n.strip())
for n in data.children["winchupgradesocket"].attrs["type"].split(",")
]
+ self.DefaultWinch = list(
+ {x.get_winch(data.children["winchupgradesocket"].attrs["default"]) for x in self.Winches} - {None}
+ )[0]
self.ignore_child("WinchUpgradeSocket")
self.ignore_attr("_noinherit")
@@ -1150,9 +1367,7 @@ class TruckTires(SnowrunnerClass):
self.TruckTire = None
def load_data(self, data: SnowRunnerXNLNode) -> None:
- self.load_child(data, "TruckTire")
- if self.TruckTire and not isinstance(self.TruckTire, list):
- self.TruckTire = [self.TruckTire]
+ self.load_child(data, "TruckTire", always_as_list=True)
self.loading_finished(data)
def update_data(self, data: SnowRunnerXNLNode) -> None:
@@ -1161,7 +1376,7 @@ class TruckTires(SnowrunnerClass):
for orig, upd in zip_longest(self.TruckTire, data.children["trucktire"]):
if orig is None:
- self.TruckTire.append(self.pak.load_class(SnowrunnerGameObjectType.TRUCK_TIRE, upd))
+ self.TruckTire.append(self.pak.load_class(SnowrunnerGameObjectType.TRUCK_TIRE, upd, dlc_name=self.DLC))
elif upd is None:
return
else:
@@ -1202,6 +1417,13 @@ class TruckWheels(SnowrunnerClass):
self.WidthRear = -1.0
self.TruckTires = None
+ def get_truck_tire(self, name: str) -> TruckTire | None:
+ for x in self.TruckTires.TruckTire:
+ if x.Name == name:
+ return x
+
+ return None
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "DamageCapacity")
self.load_attr(data, "Mass")
@@ -1276,12 +1498,21 @@ class Wheels(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader)
self.Wheel = None
+ self.DefaultTire = None
+ self.DefaultWheelType = None
def load_data(self, data: SnowRunnerXNLNode) -> None:
- self.ignore_attr("DefaultWheelType")
+ if "defaultwheeltype" in data.attrs:
+ self.DefaultWheelType = self.pak.get_object(
+ SnowrunnerGameObjectType.TRUCK_WHEELS, name=data.attrs["defaultwheeltype"]
+ )
+ self.ignore_attr("DefaultWheelType")
+ if "defaulttire" in data.attrs:
+ self.DefaultTire = self.DefaultWheelType.get_truck_tire(data.attrs["defaulttire"])
+ self.ignore_attr("DefaultTire")
+
self.ignore_attr("DefaultRim")
- self.ignore_attr("DefaultTire")
- self.load_child(data, "Wheel")
+ self.load_child(data, "Wheel", always_as_list=True)
self.loading_finished(data)
@@ -1349,8 +1580,15 @@ class WinchVariants(SnowrunnerClass):
super().__init__(pak_loader=pak_loader)
self.Winch = []
+ def get_winch(self, name: str) -> Winch | None:
+ for e in self.Winch:
+ if e.Name == name:
+ return e
+
+ return None
+
def load_data(self, data: SnowRunnerXNLNode) -> None:
- self.load_child(data, "Winch")
+ self.load_child(data, "Winch", always_as_list=True)
self.load_child(data, "WinchParams")
self.loading_finished(data)
@@ -1366,6 +1604,15 @@ class LanguageLoader(dict):
value = parts[-1].replace('"', "").replace("\\", '"')
self[key] = value
+ def get_sanitized(self, key: str, default: str | None = None) -> str:
+ if key not in self:
+ if default is not None:
+ return default
+ else:
+ raise KeyError(key)
+
+ return " ".join(x[0] + x[1:].lower() for x in re.split(r"[-_ ]+", self[key]))
+
TYPE_CLASS_MATRIX = {
"addonslots": AddonSlots,
@@ -1449,6 +1696,7 @@ class SnowrunnerGameObjectType(str, Enum):
WINCH_PARAMS = "winchparams"
WINCH_VARIANTS = "winchvariants"
+ @property
def snowrunner_class(self):
return TYPE_CLASS_MATRIX[self.value]
@@ -1532,6 +1780,7 @@ class InitialPakLoader:
self.pak_zip = get_initial_pak_zipfile(get_initial_pak_path())
self.is_loaded = False
self.languages = {}
+ self.dlc_names = {x: {} for x in SnowrunnerGameObjectType}
self.raw_data = {x: {} for x in SnowrunnerGameObjectType}
self.game_data = {x: {} for x in SnowrunnerGameObjectType}
self.templates = {x: {} for x in SnowrunnerGameObjectType}
@@ -1550,6 +1799,7 @@ class InitialPakLoader:
self,
object_type: SnowrunnerGameObjectType,
data: SnowRunnerXNLNode,
+ dlc_name: str,
parent: str | None = None,
lazy: bool = False,
) -> SnowrunnerClass | list[SnowrunnerClass] | None:
@@ -1557,37 +1807,40 @@ class InitialPakLoader:
template = self.get_object(object_type=object_type, name=parent, lazy=lazy)
if template is None:
return None
- ret = object_type.snowrunner_class().from_template(pak_loader=self, template=template)
+ ret = object_type.snowrunner_class.from_template(pak_loader=self, template=template)
+ ret.DLC = dlc_name
ret.update_data(data)
return ret
if not isinstance(data, list):
if "_template" not in data.attrs:
- ret = object_type.snowrunner_class()(pak_loader=self)
+ ret = object_type.snowrunner_class(pak_loader=self)
else:
- ret = object_type.snowrunner_class().from_template(
+ ret = object_type.snowrunner_class.from_template(
pak_loader=self, template=self.get_template(object_type=object_type, name=data.attrs["_template"])
)
+ ret.DLC = dlc_name
ret.load_data(data)
else:
ret = []
for sub_data in data:
if "_template" not in sub_data.attrs:
- ret.append(object_type.snowrunner_class()(pak_loader=self))
+ ret.append(object_type.snowrunner_class(pak_loader=self))
else:
ret.append(
- object_type.snowrunner_class().from_template(
+ object_type.snowrunner_class.from_template(
pak_loader=self,
template=self.get_template(object_type=object_type, name=sub_data.attrs["_template"]),
)
)
+ ret[-1].DLC = dlc_name
ret[-1].load_data(sub_data)
return ret
- def load_templates(self, data: dict, local: bool = False) -> None:
+ def load_templates(self, data: dict, dlc_name: str, local: bool = False) -> None:
if local:
self.templates_local = {x: {} for x in SnowrunnerGameObjectType}
@@ -1610,18 +1863,26 @@ class InitialPakLoader:
continue
if local:
self.templates_local[category][tp_name] = self.load_class(
- object_type=object_type, data=sub_data
+ object_type=object_type,
+ data=sub_data,
+ dlc_name=dlc_name,
)
else:
- self.templates[category][tp_name] = self.load_class(object_type=object_type, data=sub_data)
+ self.templates[category][tp_name] = self.load_class(
+ object_type=object_type, data=sub_data, dlc_name=dlc_name
+ )
if defer:
for tp_name, sub_data in defer:
if local:
self.templates_local[category][tp_name] = self.load_class(
- object_type=object_type, data=sub_data
+ object_type=object_type,
+ data=sub_data,
+ dlc_name=dlc_name,
)
else:
- self.templates[category][tp_name] = self.load_class(object_type=object_type, data=sub_data)
+ self.templates[category][tp_name] = self.load_class(
+ object_type=object_type, data=sub_data, dlc_name=dlc_name
+ )
elif category in self.ignore_categories:
pass # Ignore for now
@@ -1657,6 +1918,9 @@ class InitialPakLoader:
logger.error(f"Unknown Game Data Object requested: {name} in {object_type.name}")
sys.exit(1)
+ def get_object_list(self, object_type: SnowrunnerGameObjectType) -> list:
+ return list(self.game_data[object_type].values())
+
def get_sorted_object_list(
self, object_type: SnowrunnerGameObjectType, language: LanguageLoader
) -> list[tuple[str, SnowrunnerClass]]:
@@ -1664,12 +1928,13 @@ class InitialPakLoader:
sorted(self.game_data[object_type].items(), key=lambda v: v[1].get_translated_ui_name(language)),
)
- def store_raw(self, ref: str, data: dict):
+ def store_raw(self, ref: str, dlc_name: str, data: dict):
cat_in_matrix = set(data.keys()) & set(SnowrunnerGameObjectType.values())
cat_in_ignore = set(data.keys()) & self.ignore_categories
if cat_in_matrix:
self.raw_data[SnowrunnerGameObjectType(list(cat_in_matrix)[0])][ref] = data
+ self.dlc_names[SnowrunnerGameObjectType(list(cat_in_matrix)[0])][ref] = dlc_name
elif cat_in_ignore:
pass # not sure what to do with this, yet
else:
@@ -1686,6 +1951,8 @@ class InitialPakLoader:
for member in self.pak_zip.infolist():
if not member.filename.endswith(".xml"):
continue
+
+ dlc_name = member.filename.split("\\")[2] if "_dlc" in member.filename else None
filename_ref = member.filename.split("\\")[-1].replace(".xml", "")
logger.info(f" - {member.filename}")
with self.pak_zip.open(member) as d_file:
@@ -1695,13 +1962,13 @@ class InitialPakLoader:
if len(data) == 1 and "_templates" in data:
logger.info(f" - -> Loading templates from {member.filename}")
- self.load_templates(data["_templates"].children)
+ self.load_templates(data["_templates"].children, dlc_name=dlc_name)
continue
elif len(data) > 3:
logger.error(f"Multiple keys?! ({list(data.keys())}")
sys.exit(1)
- self.store_raw(filename_ref, data)
+ self.store_raw(filename_ref, dlc_name, data)
deferred_loads = []
logger.info("Loading GameData")
@@ -1715,9 +1982,13 @@ class InitialPakLoader:
logger.info(f" - {ref}")
if "_templates" in data:
- self.load_templates(data["_templates"].children, local=True)
+ self.load_templates(
+ data["_templates"].children, local=True, dlc_name=self.dlc_names[object_type][ref]
+ )
- self.game_data[object_type][ref] = self.load_class(object_type, data[category])
+ self.game_data[object_type][ref] = self.load_class(
+ object_type, data[category], dlc_name=self.dlc_names[object_type][ref]
+ )
del self.raw_data[object_type]
@@ -1726,9 +1997,15 @@ class InitialPakLoader:
for object_type, ref, data in deferred_loads:
logger.info(f" - {ref}")
if "_templates" in data:
- self.load_templates(data["_templates"].children, local=True)
+ self.load_templates(
+ data["_templates"].children, local=True, dlc_name=self.dlc_names[object_type][ref]
+ )
sr_class = self.load_class(
- object_type, data[category], parent=data["_parent"].get_attr("file"), lazy=True
+ object_type,
+ data[category],
+ parent=data["_parent"].get_attr("file"),
+ lazy=True,
+ dlc_name=self.dlc_names[object_type][ref],
)
if sr_class is None:
still_deferred_loads.append((object_type, ref, data))
@@ -1743,7 +2020,9 @@ class InitialPakLoader:
logger.info(f" - {object_type.name}, {ref}")
if "_templates" in data:
self.load_templates(data["_templates"].children, local=True)
- self.game_data[object_type][ref] = self.load_class(object_type, data[category])
+ self.game_data[object_type][ref] = self.load_class(
+ object_type, data[category], dlc_name=self.dlc_names[object_type][ref]
+ )
deferred_loads = []
for cat, v in self.raw_data.items():
diff --git a/output.py b/output.py
index f0f00b6..8879051 100644
--- a/output.py
+++ b/output.py
@@ -1,6 +1,14 @@
from __future__ import annotations
import os
-from loader import InitialPakLoader, LanguageLoader, SnowrunnerGameObjectType, SnowrunnerClass, GameData
+from constants import DLC_NAMES
+from loader import (
+ InitialPakLoader,
+ LanguageLoader,
+ SnowrunnerGameObjectType,
+ TruckAddonCategory,
+ GameData,
+ SnowrunnerClass,
+)
from logging import getLogger
logger = getLogger(__name__)
@@ -13,13 +21,17 @@ def get_head(language: LanguageLoader, category: str, name: str | None = None) -
SnowRunnerDB - {category}"""
+ (f""" - {name}""" if name is not None else "")
+ f"""
+
+
+
@@ -44,50 +56,154 @@ def write_html(
def write_trucks(pak: InitialPakLoader, destination_directory: str, language: LanguageLoader):
- index_table = ""
-
- truck_types = {}
+ index_content = "
Trucks \n"
+ index_content += f"
\n"
+ index_content += (
+ f" \n"
+ f" Name \n"
+ f" Class \n"
+ f" DLC \n"
+ f" Unlock by \n"
+ f" Region \n"
+ f" Price \n"
+ f" Weight \n"
+ f" Drivetrain \n"
+ f" AWD \n"
+ f" DiffLock \n"
+ f" \n"
+ )
+ index_content += f" \n"
for reference, truck in pak.get_sorted_object_list(SnowrunnerGameObjectType.TRUCK, language):
if truck.Type != "Truck":
continue
- if truck.TruckData.TruckType not in truck_types:
- truck_types[truck.TruckData.TruckType] = [(reference, truck)]
- else:
- truck_types[truck.TruckData.TruckType].append((reference, truck))
-
truck_file = os.path.join(destination_directory, reference + ".html")
truck_content = f" {truck.get_translated_ui_name(language)} "
+ truck_content += f" \n"
+ try:
+ truck_content += f" DLC {DLC_NAMES[truck.DLC]} \n"
+ except KeyError:
+ print(f"DEBUG: DLC {truck.DLC} for Truck {truck.get_translated_ui_name(language)}")
+ exit(1)
+ truck_content += f" Weight {truck.Weight}kg \n"
+ truck_content += f" AWD {language[truck.AWDInfo.value]} \n"
+ truck_content += f" DiffLock {language[truck.DiffLockInfo.value]} \n"
+ truck_content += f" Def Engine {truck.TruckData.DefaultEngine.get_translated_ui_name(language)} \n"
+ truck_content += f" Def Gearbox {truck.TruckData.DefaultGearbox.get_translated_ui_name(language)} \n"
+ truck_content += f" Def Suspension {truck.TruckData.DefaultSuspension.get_translated_ui_name(language)} \n"
+ truck_content += f" Def Winch {truck.TruckData.DefaultWinch.get_translated_ui_name(language)} \n"
+ truck_content += f" Def Tire {truck.TruckData.Wheels.DefaultTire.get_translated_ui_name(language)} \n"
+ truck_content += f"
\n"
# Engines
truck_content += f" Engines \n"
- engines = [x for y in truck.TruckData.Engines for x in y.Engines]
- truck_content += f" \n"
- truck_content += f" Name Price Torque Base Fuel Consumption Unlock By \n"
+ engines = [x for y in truck.TruckData.Engines for x in y.Engine]
+ truck_content += f" \n"
+ truck_content += (
+ f" \n"
+ f" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" Base Fuel Consumption \n"
+ f" Torque \n"
+ f" Power-to-Weight \n"
+ f" \n"
+ )
+ truck_content += f" \n"
# Power2Weight
# Power2Fuel
for engine in sorted(engines, key=lambda x: x.Torque):
truck_content += (
f" "
- f"{engine.get_translated_ui_name(language)} "
- f"{engine.GameData.Price}"
- f" {engine.Torque} "
- f"{engine.FuelConsumption} "
- f"{engine.GameData.get_unlock_str()} "
- f" \n"
+ f" {engine.get_translated_ui_name(language)} "
+ f" {engine.GameData.Price}"
+ f" {engine.GameData.get_unlock_str()} "
+ f" {engine.FuelConsumption} "
+ f" {engine.Torque} "
+ f" {engine.Torque / truck.Weight:.2f} "
+ f" \n"
)
- truck_content += f"
\n"
+ truck_content += f"
\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" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" Fine-Tunable \n"
+ f" L- \n"
+ f" L \n"
+ f" L+ \n"
+ f" H \n"
+ f" Gears \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" {gearbox.get_translated_ui_name(language)} \n"
+ truck_content += f" {gearbox.Price} \n"
+ truck_content += f" {gearbox.GameData.get_unlock_str()} \n"
+ truck_content += (
+ f" {'✓' if gearbox.GearboxParams.IsManualLowGear else '˙'} \n"
+ )
+ truck_content += (
+ f" {'✓' if gearbox.GearboxParams.IsLowerMinusGearExists else '˙'} \n"
+ )
+ truck_content += (
+ f" {'✓' if gearbox.GearboxParams.IsLowerGearExists else '˙'} \n"
+ )
+ truck_content += (
+ f" {'✓' if gearbox.GearboxParams.IsLowerPlusGearExists else '˙'} \n"
+ )
+ truck_content += (
+ f" {'✓' if gearbox.GearboxParams.IsHighGearExists else '˙'} \n"
+ )
+ truck_content += f" {len(gearbox.Gear)} \n"
+ truck_content += f" \n"
+ truck_content += f"
\n"
+
+ truck_content += f" Fuel Consumption \n"
+ truck_content += f" \n"
+ truck_content += (
+ f" \n"
+ f" Name \n"
+ f" Fuel Consumption \n"
+ f" AWD Fuel Modifier \n"
+ )
+ for i in range(max_gear_count):
+ gear_num = str(i + 1) if i > 0 else "L+/1"
+ truck_content += f" {gear_num} \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" {gearbox.get_translated_ui_name(language)} \n"
+ truck_content += f" {gearbox.FuelConsumption} \n"
+ truck_content += f" {gearbox.AWDConsumptionModifier} \n"
+ for i in range(max_gear_count):
+ if i >= len(gearbox.Gear):
+ truck_content += f" - \n"
+ else:
+ truck_content += f" {gearbox.Gear[i].FuelModifier} \n"
+ truck_content += f" \n"
+
+ truck_content += f"
\n"
+
# Suspensions
truck_content += f" Suspensions \n"
- # Winches
- truck_content += f" Winches \n"
# Wheels
- truck_content += f" Wheels \n"
+ truck_content += f" Tires \n"
wheel_cat = {}
scales = set()
for cw in truck.TruckData.CompatibleWheels:
@@ -124,6 +240,124 @@ def write_trucks(pak: InitialPakLoader, destination_directory: str, language: La
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" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" \n"
+ )
+ for addon in sorted(diff_lock_addons, key=lambda v: v.get_translated_ui_name(language)):
+ truck_content += (
+ f" \n"
+ f" {addon.get_translated_ui_name(language)} \n"
+ f" {addon.Price} \n"
+ f" {addon.GameData.get_unlock_str()} \n"
+ f" \n"
+ )
+ truck_content += f"
\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" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" Mass \n"
+ f" \n"
+ )
+ for addon in sorted(snorkel_addons, key=lambda v: v.get_translated_ui_name(language)):
+ truck_content += (
+ f" \n"
+ f" {addon.get_translated_ui_name(language)} \n"
+ f" {addon.Price} \n"
+ f" {addon.GameData.get_unlock_str()} \n"
+ f" {addon.PhysicsModel.Weight}kg \n"
+ f" \n"
+ )
+ truck_content += f"
\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" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" Mass \n"
+ f" \n"
+ )
+ for addon in sorted(awd_addons, key=lambda v: v.get_translated_ui_name(language)):
+ truck_content += (
+ f" \n"
+ f" {addon.get_translated_ui_name(language)} \n"
+ f" {addon.Price} \n"
+ f" {addon.GameData.get_unlock_str()} \n"
+ f" {addon.PhysicsModel.Weight}kg \n"
+ f" \n"
+ )
+ truck_content += f"
\n"
+
+ # Frame Addons
+
+ truck_content += f" Frame Addons \n"
+ truck_content += f" \n"
+ truck_content += (
+ f" \n"
+ f" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" Mass \n"
+ f" Cargo Slots \n"
+ f" Repair Points \n"
+ f" Spare Wheels \n"
+ f" Fuel Capacity \n"
+ f" Water Capacity \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" {truck_addon.get_translated_ui_name(language)} \n"
+ truck_content += f" {truck_addon.Price} \n"
+ truck_content += f" {truck_addon.GameData.get_unlock_str()} \n"
+ truck_content += f" {truck_addon.PhysicsModel.Weight}kg \n"
+ truck_content += f" {truck_addon.AddonSlots.Quantity if truck_addon.AddonSlots is not None else ' '} \n"
+ truck_content += f" {truck_addon.TruckData.RepairsCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.RepairsCapacity > 0 else ' '} \n"
+ truck_content += f" {truck_addon.TruckData.WheelRepairsCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.WheelRepairsCapacity > 0 else ' '} \n"
+ truck_content += f" {truck_addon.TruckData.FuelCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.FuelCapacity > 0 else ' '} \n"
+ truck_content += f" {truck_addon.TruckData.WaterCapacity if truck_addon.TruckData is not None and truck_addon.TruckData.WaterCapacity > 0 else ' '} \n"
+ truck_content += f" \n"
+ truck_content += f"
\n"
+
+ truck_content += f" \n"
+
write_html(
truck_file,
language=language,
@@ -132,20 +366,99 @@ def write_trucks(pak: InitialPakLoader, destination_directory: str, language: La
name=truck.get_translated_ui_name(language),
)
- index_content = " Trucks \n"
- for truck_type in sorted(truck_types):
- index_content += f" {truck_type} \n"
- index_content += f" \n"
- index_content += f" Name \n"
- for reference, truck in sorted(truck_types[truck_type], key=lambda v: v[1].get_translated_ui_name(language)):
- index_content += f" {truck.get_translated_ui_name(language)} \n"
- index_content += f"
\n"
+ 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" Name \n"
+ f" Price \n"
+ f" Unlock By \n"
+ f" Mass \n"
+ f" Cargo Slots \n"
+ f" Repair Points \n"
+ f" Spare Wheels \n"
+ f" Fuel Capacity \n"
+ f" Water Capacity \n"
+ f" Available for \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" {frame_addon.get_translated_ui_name(language)} \n"
+ index_content += f" {frame_addon.Price} \n"
+ index_content += f" {frame_addon.GameData.get_unlock_str()} \n"
+ index_content += f" {frame_addon.PhysicsModel.Weight}kg \n"
+ index_content += f" {frame_addon.AddonSlots.Quantity if frame_addon.AddonSlots is not None else ' '} \n"
+ index_content += f" {frame_addon.TruckData.RepairsCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.RepairsCapacity > 0 else ' '} \n"
+ index_content += f" {frame_addon.TruckData.WheelRepairsCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.WheelRepairsCapacity > 0 else ' '} \n"
+ index_content += f" {frame_addon.TruckData.FuelCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.FuelCapacity > 0 else ' '} \n"
+ index_content += f" {frame_addon.TruckData.WaterCapacity if frame_addon.TruckData is not None and frame_addon.TruckData.WaterCapacity > 0 else ' '} \n"
+
+ truck_links = ", ".join(
+ f'{truck.get_translated_ui_name(language)} '
+ for ref, truck in available_trucks
+ )
+ index_content += f" {truck_links} \n"
+
+ index_content += f" \n"
+
+ index_content += f" \n"
+ index_content += f"
\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)
@@ -157,4 +470,12 @@ def create_html(pak: InitialPakLoader, destination_directory: str = "output/"):
if not os.path.exists(language_path):
os.mkdir(language_path)
- write_trucks(pak=pak, destination_directory=language_path, language=language_data)
+ 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)