cargo, partly

This commit is contained in:
Stefan Harmuth 2026-08-28 13:48:51 +02:00
parent fe6e55a891
commit 8b97873f57
3 changed files with 314 additions and 89 deletions

View File

@ -1,3 +1,11 @@
from enum import Enum
LANGUAGES = {
"english": ("en", r"[strings]\strings_english.str"),
"korean": ("kr", r"[strings]\strings_korean.str"),
}
DEFAULT_LANGUAGE = "english"
COUNTRYS = {} COUNTRYS = {}
DLC_NAMES = { DLC_NAMES = {
@ -62,3 +70,34 @@ DLC_OVERRIDES = {
"UI_VEHICLE_ZIKZ_612H_MASTODONT_NAME": "dlc_mastodon", "UI_VEHICLE_ZIKZ_612H_MASTODONT_NAME": "dlc_mastodon",
"UI_VEHICLE_KENWORTH_W990_NAME": "dlc_w990", "UI_VEHICLE_KENWORTH_W990_NAME": "dlc_w990",
} }
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"

236
loader.py
View File

@ -1,5 +1,5 @@
from __future__ import annotations from __future__ import annotations
from constants import DLC_OVERRIDES from constants import *
from decimal import Decimal, ROUND_HALF_UP from decimal import Decimal, ROUND_HALF_UP
from enum import Enum from enum import Enum
from html import parser as html_parser from html import parser as html_parser
@ -10,13 +10,6 @@ import os
import re import re
import sys import sys
LOAD_LIST = "pak.load_list"
LANGUAGES = {
"english": ("en", r"[strings]\strings_english.str"),
"korean": ("kr", r"[strings]\strings_korean.str"),
}
DEFAULT_LANGUAGE = "english"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
html_parser.starttagopen = re.compile("<[a-zA-Z_]") html_parser.starttagopen = re.compile("<[a-zA-Z_]")
html_parser.endtagopen = re.compile("</[a-zA-Z_]") html_parser.endtagopen = re.compile("</[a-zA-Z_]")
@ -59,37 +52,6 @@ locatestarttagend_tolerant = re.compile(
endtagfind = re.compile(r"</\s*([a-zA-Z_][-.a-zA-Z0-9:_]*)\s*>") 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(): def get_initial_pak_path():
return os.path.join( return os.path.join(
str(os.path.expanduser("~")), str(os.path.expanduser("~")),
@ -219,10 +181,10 @@ class SnowrunnerClass:
self.load_desc(data.children["uidesc"]) self.load_desc(data.children["uidesc"])
def load_data(self, data: SnowRunnerXNLNode) -> None: def load_data(self, data: SnowRunnerXNLNode) -> None:
raise NotImplementedError() raise NotImplementedError(f"load_data() in {self.__class__.__name__}()")
def update_data(self, data: SnowRunnerXNLNode) -> None: def update_data(self, data: SnowRunnerXNLNode) -> None:
raise NotImplementedError() raise NotImplementedError(f"update_data() in {self.__class__.__name__}()")
def ignore_attr(self, name: str) -> None: def ignore_attr(self, name: str) -> None:
self.__attrs_handled.add(name.lower()) self.__attrs_handled.add(name.lower())
@ -252,7 +214,7 @@ class SnowrunnerClass:
self.load_desc(data.children[name.lower()]) self.load_desc(data.children[name.lower()])
return return
if name == "GameData": if name.lower() == "gamedata":
self.load_game_data(data.children[name.lower()]) self.load_game_data(data.children[name.lower()])
return return
@ -274,9 +236,7 @@ class SnowrunnerClass:
) )
def loading_finished(self, data: SnowRunnerXNLNode): def loading_finished(self, data: SnowRunnerXNLNode):
attr_set = set(data.attrs.keys()) attr_set = set(data.attrs.keys()) - {"_template"}
if "_template" in attr_set:
attr_set.remove("_template")
if len(attr_set - self.__attrs_handled) > 0: if len(attr_set - self.__attrs_handled) > 0:
logger.error( logger.error(
f"{self.__class__.__name__}(): Unhandled Attributes: {[attr_set - self.__attrs_handled]}, (Handled: {[self.__attrs_handled]}, Exist: {[attr_set]}" f"{self.__class__.__name__}(): Unhandled Attributes: {[attr_set - self.__attrs_handled]}, (Handled: {[self.__attrs_handled]}, Exist: {[attr_set]}"
@ -357,15 +317,18 @@ class SnowrunnerClass:
class AddonSlots(SnowrunnerClass): class AddonSlots(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader): def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader) super().__init__(pak_loader=pak_loader)
self.AllowedCargo = None
self.InitialOffset = "(0;0;0)" self.InitialOffset = "(0;0;0)"
self.Quantity = 0 self.Quantity = 0
self.UseTrailerFrame = False self.UseTrailerFrame = False
def load_data(self, data: SnowRunnerXNLNode) -> None: def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "AllowedCargo")
self.load_attr(data, "InitialOffset") self.load_attr(data, "InitialOffset")
self.load_attr(data, "Quantity") self.load_attr(data, "Quantity")
self.load_attr(data, "UseTrailerFrame") self.load_attr(data, "UseTrailerFrame")
self.ignore_attr("OffsetStep") self.ignore_attr("OffsetStep")
self.ignore_attr("ParentFrame")
self.ignore_attr("ParentFrames") self.ignore_attr("ParentFrames")
self.loading_finished(data) self.loading_finished(data)
@ -436,10 +399,10 @@ class Body(SnowrunnerClass):
self.load_attr(data, "Friction") self.load_attr(data, "Friction")
self.load_attr(data, "Mass") self.load_attr(data, "Mass")
self.load_attr(data, "Name") self.load_attr(data, "Name")
self.load_attr(data, "IsCapsuleCDT")
self.load_attr(data, "DamageMult") self.load_attr(data, "DamageMult")
self.load_attr(data, "RollingFrictionMultiplier") self.load_attr(data, "RollingFrictionMultiplier")
self.load_attr(data, "ImpactType") self.load_attr(data, "ImpactType")
self.load_attr(data, "IsCapsuleCDT")
self.load_attr(data, "LinearDamping") self.load_attr(data, "LinearDamping")
self.load_attr(data, "AngularDamping") self.load_attr(data, "AngularDamping")
self.load_attr(data, "UseDirtTint") self.load_attr(data, "UseDirtTint")
@ -461,8 +424,17 @@ class Body(SnowrunnerClass):
self.load_attr(data, "NetSyncUseGravity") self.load_attr(data, "NetSyncUseGravity")
self.load_attr(data, "NoPackWheels") self.load_attr(data, "NoPackWheels")
self.ignore_attr("ExplicitParentFrame") self.ignore_attr("ExplicitParentFrame")
self.ignore_attr("FrictionPreset")
self.ignore_attr("HideDistance")
self.ignore_attr("IsComplexPhysModel")
self.ignore_attr("IsGameAsphalt")
self.ignore_attr("IsStaticSoil")
self.ignore_attr("NetSync") self.ignore_attr("NetSync")
self.ignore_attr("ParentFrame") self.ignore_attr("ParentFrame")
self.ignore_attr("SCapsuleCdt")
self.ignore_attr("ShadowHideDistance")
self.ignore_attr("template")
self.load_child(data, "Body", always_as_list=True) self.load_child(data, "Body", always_as_list=True)
self.ignore_child("Constraint") self.ignore_child("Constraint")
self.ignore_child("Motor") self.ignore_child("Motor")
@ -581,16 +553,21 @@ class FuelMass(SnowrunnerClass):
class GameData(SnowrunnerClass): class GameData(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader): def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader) super().__init__(pak_loader=pak_loader)
self.AddonSlots = None
self.AddonSockets = None self.AddonSockets = None
self.CameraPreset = None self.CameraPreset = None
self.Category = None self.Category = None
self.Country = "" self.Country = ""
self.ExcludeAddons = "" self.ExcludeAddons = ""
self.ExcludedCargoTypes = "" self.ExcludedCargoTypes = ""
self.InstallSlot = None
self.InstallSocket = None self.InstallSocket = None
self.IsCustomizable = False self.IsCustomizable = False
self.IsDoubleTrailer = False self.IsDoubleTrailer = False
self.IsQuest = False self.IsQuest = False
self.LoadType = None
self.LoadAddons = None
self.PackSlotsNumber = 0
self.Price = 0 self.Price = 0
self.UnlockByRank = -1 self.UnlockByRank = -1
self.UnlockByExploration = False self.UnlockByExploration = False
@ -635,29 +612,43 @@ class GameData(SnowrunnerClass):
self.load_attr(data, "RecreateOnZoneChange") self.load_attr(data, "RecreateOnZoneChange")
self.load_attr(data, "TrialsToUnlock") self.load_attr(data, "TrialsToUnlock")
self.load_attr(data, "IncludedCargoTypes") self.load_attr(data, "IncludedCargoTypes")
self.load_attr(data, "PackSlotsNumber")
self.load_attr(data, "LoadAddons")
if "loadtype" in data.attrs:
self.LoadType = self.pak.get_object(SnowrunnerGameObjectType.CARGO_TYPE, data.attrs["loadtype"])
self.ignore_attr("LoadType")
self.ignore_attr("_noinherit") self.ignore_attr("_noinherit")
self.ignore_attr("AddonUnlockByObjective") self.ignore_attr("AddonUnlockByObjective")
self.ignore_attr("FrameAlign") self.ignore_attr("FrameAlign")
self.ignore_attr("FrameAlignOffset") self.ignore_attr("FrameAlignOffset")
self.ignore_attr("LegacyAlignementTruckAfterGateway") self.ignore_attr("LegacyAlignementTruckAfterGateway")
self.ignore_attr("ManualLoadSpawnOffsetX")
self.ignore_attr("ManualLoadSpawnOffsetY")
self.ignore_attr("Recallable") self.ignore_attr("Recallable")
self.ignore_attr("ResetTruckAfterGateway") self.ignore_attr("ResetTruckAfterGateway")
self.children = data.children self.children = data.children
self.load_child(data, "AddonSockets", always_as_list=True) self.load_child(data, "AddonSockets", always_as_list=True)
self.load_child(data, "AddonSlots")
self.load_child(data, "UiDesc") self.load_child(data, "UiDesc")
if "installsocket" in data.children: if self.InstallSocket is None:
self.InstallSocket = data.children["installsocket"].attrs.get("type", self.InstallSocket) self.load_child(data, "InstallSocket")
elif "installsocket" in data.children:
self.InstallSocket.update_data(data.children["installsocket"])
self.ignore_child("InstallSocket") self.ignore_child("InstallSocket")
if self.InstallSlot is None:
self.ignore_child("AddonSlots") self.load_child(data, "InstallSlot")
elif "installslot" in data.children:
self.InstallSlot.update_data(data.children["installslot"])
self.ignore_child("InstallSlot")
self.ignore_child("AddonType") self.ignore_child("AddonType")
self.ignore_child("CenteringAngularVelocity") self.ignore_child("CenteringAngularVelocity")
self.ignore_child("CraneSocket") self.ignore_child("CraneSocket")
self.ignore_child("CustomizationCameras") self.ignore_child("CustomizationCameras")
self.ignore_child("GearboxParams") self.ignore_child("GearboxParams")
self.ignore_child("InstallSlot")
self.ignore_child("LoadArea") self.ignore_child("LoadArea")
self.ignore_child("LoadPoint")
self.ignore_child("LongLogsAlignTarget") self.ignore_child("LongLogsAlignTarget")
self.ignore_child("RequiredAddon") self.ignore_child("RequiredAddon")
self.ignore_child("RequiredAddonType") self.ignore_child("RequiredAddonType")
@ -794,7 +785,7 @@ class InstallSlot(SnowrunnerClass):
super().__init__(pak_loader=pak_loader) super().__init__(pak_loader=pak_loader)
self.CargoAddonSubtype = "" self.CargoAddonSubtype = ""
self.CargoLength = 0 self.CargoLength = 0
self.CargoType = "" self.CargoType = None
self.CargoValue = 1 self.CargoValue = 1
self.ManualLoads = 0 self.ManualLoads = 0
self.NoPackWheels = False self.NoPackWheels = False
@ -803,7 +794,9 @@ class InstallSlot(SnowrunnerClass):
def load_data(self, data: SnowRunnerXNLNode) -> None: def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "CargoAddonSubtype") self.load_attr(data, "CargoAddonSubtype")
self.load_attr(data, "CargoLength") self.load_attr(data, "CargoLength")
self.load_attr(data, "CargoType") if "cargotype" in data.attrs:
self.CargoType = self.pak.get_object(SnowrunnerGameObjectType.CARGO_TYPE, data.attrs["cargotype"])
self.ignore_attr("CargoType")
self.load_attr(data, "CargoValue") self.load_attr(data, "CargoValue")
self.load_attr(data, "ManualLoads") self.load_attr(data, "ManualLoads")
self.load_attr(data, "NoPackWheels") self.load_attr(data, "NoPackWheels")
@ -823,12 +816,66 @@ class InstallSocket(SnowrunnerClass):
self.ignore_attr("Price") # SERIOUSLY, SABER self.ignore_attr("Price") # SERIOUSLY, SABER
self.ignore_attr("UnlockByRank") # VALIDATE self.ignore_attr("UnlockByRank") # VALIDATE
self.ignore_attr("UnlockByExploration") # YOUR INPUTS self.ignore_attr("UnlockByExploration") # YOUR INPUTS
self.ignore_attr("CablesPos")
self.load_attr(data, "CameraPreset") self.load_attr(data, "CameraPreset")
self.load_attr(data, "Type") self.load_attr(data, "Type")
self.load_attr(data, "ParentFrame") self.load_attr(data, "ParentFrame")
self.load_attr(data, "Offset") self.load_attr(data, "Offset")
self.loading_finished(data) self.loading_finished(data)
def update_data(self, data: SnowRunnerXNLNode) -> None:
self.load_data(data)
class ModelBrand(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader)
self.PhysicsModel = None
def load_data(self, data: SnowRunnerXNLNode) -> None:
self.ignore_attr("acceptzoneborderdecals")
self.ignore_attr("applynormalinreference")
self.ignore_attr("breaktype")
self.ignore_attr("castnightlightmapshadow")
self.ignore_attr("castdaylightmapshadow")
self.ignore_attr("clipcamera")
self.ignore_attr("clipcamerabridge")
self.ignore_attr("dayshadowoffsetmult")
self.ignore_attr("deactivationphantomext")
self.ignore_attr("deleteonbreak")
self.ignore_attr("disableshadows")
self.ignore_attr("distskippercentage")
self.ignore_attr("dynamicmodel")
self.ignore_attr("dynamicmodeldistactivation")
self.ignore_attr("fence")
self.ignore_attr("inbackground")
self.ignore_attr("instanced")
self.ignore_attr("instancedmodel")
self.ignore_attr("instancedasmodel")
self.ignore_attr("IsComplexPhysModel")
self.ignore_attr("iskeepphysmodelalways")
self.ignore_attr("nightlightingshadowsallowcap")
self.ignore_attr("nosoftcontacts")
self.ignore_attr("skipextrablocks")
self.ignore_attr("usephysicsforinstancing")
self.load_child(data, "GameData")
self.load_child(data, "PhysicsModel")
self.ignore_child("AnimSubset")
self.ignore_child("LandMark")
self.ignore_child("MeshLod")
self.ignore_child("Occlusion")
self.ignore_child("PreBuild")
self.ignore_child("SnapPoint")
self.ignore_child("StaticLight")
self.ignore_child("StaticLights")
self.ignore_child("Subset")
self.ignore_child("TrackEvents")
self.loading_finished(data)
def update_data(self, data: SnowRunnerXNLNode) -> None:
self.load_data(data)
class Multiplier(SnowrunnerClass): class Multiplier(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader): def __init__(self, pak_loader: InitialPakLoader):
@ -861,6 +908,7 @@ class PhysicsModel(SnowrunnerClass):
self.load_attr(data, "_noinherit") self.load_attr(data, "_noinherit")
self.load_attr(data, "Mesh") self.load_attr(data, "Mesh")
self.load_child(data, "Body") self.load_child(data, "Body")
self.ignore_child("Constraint")
self.ignore_child("FarmingBoundingBox1") self.ignore_child("FarmingBoundingBox1")
self.ignore_child("FarmingBoundingBox2") self.ignore_child("FarmingBoundingBox2")
self.ignore_child("FarmingBoundingBox3") self.ignore_child("FarmingBoundingBox3")
@ -869,6 +917,7 @@ class PhysicsModel(SnowrunnerClass):
self.ignore_child("FarmingBoundingBox6") self.ignore_child("FarmingBoundingBox6")
self.ignore_child("FarmingBoundingBox7") self.ignore_child("FarmingBoundingBox7")
self.ignore_child("FarmingBoundingBox8") self.ignore_child("FarmingBoundingBox8")
self.ignore_child("Flare")
self.ignore_child("NetSync") self.ignore_child("NetSync")
self.loading_finished(data) self.loading_finished(data)
@ -888,15 +937,19 @@ class RequiredAddon(SnowrunnerClass):
class Socket(SnowrunnerClass): class Socket(SnowrunnerClass):
def __init__(self, pak_loader: InitialPakLoader): def __init__(self, pak_loader: InitialPakLoader):
super().__init__(pak_loader=pak_loader) super().__init__(pak_loader=pak_loader)
self.InCockpit = False self.InCockpit: bool = False
self.Names = "" self.Names: list[str] = []
self.NamesBlock = "" self.NamesBlock: list[str] = []
self.RequiredHitches = None self.RequiredHitches = None
def load_data(self, data: SnowRunnerXNLNode) -> None: def load_data(self, data: SnowRunnerXNLNode) -> None:
self.load_attr(data, "InCockpit") self.load_attr(data, "InCockpit")
self.load_attr(data, "Names") if "names" in data.attrs:
self.load_attr(data, "NamesBlock") self.Names = [x.strip() for x in data.attrs["names"].split(",")]
self.ignore_attr("Names")
if "namesblock" in data.attrs:
self.NamesBlock = [x.strip() for x in data.attrs["namesblock"].split(",")]
self.ignore_attr("NamesBlock")
self.ignore_attr("AddonShift") self.ignore_attr("AddonShift")
self.ignore_attr("Dir") self.ignore_attr("Dir")
self.ignore_attr("Offset") self.ignore_attr("Offset")
@ -988,6 +1041,8 @@ class Truck(SnowrunnerClass):
self.TruckData = None self.TruckData = None
self.Type = "Truck" self.Type = "Truck"
self.WaterMass = None self.WaterMass = None
self.__addon_socket_names = None
self.__truck_addon_list = None
@property @property
def TireCount(self) -> int: def TireCount(self) -> int:
@ -1030,14 +1085,22 @@ class Truck(SnowrunnerClass):
) )
def get_addon_socket_names(self) -> list[str]: 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]] if self.__addon_socket_names is None:
self.__addon_socket_names = [
n for s in [y for x in self.GameData.AddonSockets for y in x.Socket] for n in s.Names
]
return self.__addon_socket_names
def get_available_truck_addons(self, category: TruckAddonCategory) -> list[TruckAddon]: def get_available_truck_addons(self, category: TruckAddonCategory) -> list[TruckAddon]:
return [ if self.__truck_addon_list is None:
ta self.__truck_addon_list = [
for ta in self.pak.get_object_list(SnowrunnerGameObjectType.TRUCK_ADDON) ta
if ta.GameData.InstallSocket in self.get_addon_socket_names() and ta.GameData.Category == category for ta in self.pak.get_object_list(SnowrunnerGameObjectType.TRUCK_ADDON)
] if ta.GameData.InstallSocket and ta.GameData.InstallSocket.Type in self.get_addon_socket_names()
]
return [x for x in self.__truck_addon_list if x.GameData.Category == category]
def get_default_truck_addons(self) -> list[TruckAddon]: def get_default_truck_addons(self) -> list[TruckAddon]:
return [x.DefaultAddon for x in self.GameData.AddonSockets if x.DefaultAddon is not None] return [x.DefaultAddon for x in self.GameData.AddonSockets if x.DefaultAddon is not None]
@ -1632,6 +1695,7 @@ TYPE_CLASS_MATRIX = {
"gearboxvariants": GearboxVariants, "gearboxvariants": GearboxVariants,
"installslot": InstallSlot, "installslot": InstallSlot,
"installsocket": InstallSocket, "installsocket": InstallSocket,
"modelbrand": ModelBrand,
"multiplier": Multiplier, "multiplier": Multiplier,
"physicsmodel": PhysicsModel, "physicsmodel": PhysicsModel,
"requiredaddon": RequiredAddon, "requiredaddon": RequiredAddon,
@ -1674,6 +1738,7 @@ class SnowrunnerGameObjectType(str, Enum):
GEARBOX_VARIANTS = "gearboxvariants" GEARBOX_VARIANTS = "gearboxvariants"
INSTALL_SLOT = "installslot" INSTALL_SLOT = "installslot"
INSTALL_SOCKET = "installsocket" INSTALL_SOCKET = "installsocket"
MODEL_BRAND = "modelbrand"
MULTIPLIER = "multiplier" MULTIPLIER = "multiplier"
PHYSICS_MODEL = "physicsmodel" PHYSICS_MODEL = "physicsmodel"
REQUIRED_ADDON = "requiredaddon" REQUIRED_ADDON = "requiredaddon"
@ -1712,6 +1777,7 @@ class SnowrunnerGameObjectType(str, Enum):
class InitialPakLoader: class InitialPakLoader:
game_data_load_order = [ game_data_load_order = [
"cargotype", "cargotype",
"modelbrand",
"enginevariants", "enginevariants",
"gearboxvariants", "gearboxvariants",
"suspensionsetvariants", "suspensionsetvariants",
@ -1754,7 +1820,6 @@ class InitialPakLoader:
"materialtype", "materialtype",
"mainheadlight", "mainheadlight",
"model", "model",
"modelbrand",
"motor", "motor",
"mudguard", "mudguard",
"mudtype", "mudtype",
@ -1861,28 +1926,23 @@ class InitialPakLoader:
if not isinstance(sub_data, list) and "_template" in sub_data.attrs: if not isinstance(sub_data, list) and "_template" in sub_data.attrs:
defer.append((tp_name, sub_data)) defer.append((tp_name, sub_data))
continue continue
template = self.load_class(object_type=object_type, data=sub_data, dlc_name=dlc_name)
if isinstance(template, list):
template = template[-1]
if local: if local:
self.templates_local[category][tp_name] = self.load_class( self.templates_local[category][tp_name] = template
object_type=object_type,
data=sub_data,
dlc_name=dlc_name,
)
else: else:
self.templates[category][tp_name] = self.load_class( self.templates[category][tp_name] = template
object_type=object_type, data=sub_data, dlc_name=dlc_name
)
if defer: if defer:
for tp_name, sub_data in defer: for tp_name, sub_data in defer:
template = self.load_class(object_type=object_type, data=sub_data, dlc_name=dlc_name)
if isinstance(template, list):
template = template[-1]
if local: if local:
self.templates_local[category][tp_name] = self.load_class( self.templates_local[category][tp_name] = template
object_type=object_type,
data=sub_data,
dlc_name=dlc_name,
)
else: else:
self.templates[category][tp_name] = self.load_class( self.templates[category][tp_name] = template
object_type=object_type, data=sub_data, dlc_name=dlc_name
)
elif category in self.ignore_categories: elif category in self.ignore_categories:
pass # Ignore for now pass # Ignore for now
@ -1892,17 +1952,18 @@ class InitialPakLoader:
def get_template(self, object_type: SnowrunnerGameObjectType, name: str) -> SnowrunnerClass: def get_template(self, object_type: SnowrunnerGameObjectType, name: str) -> SnowrunnerClass:
name = name.lower() name = name.lower()
if object_type in self.templates_local and name in self.templates_local[object_type]: if name in self.templates_local[object_type]:
return self.templates_local[object_type][name] return self.templates_local[object_type][name]
elif object_type in self.templates and name in self.templates[object_type]: elif name in self.templates[object_type]:
return self.templates[object_type][name] return self.templates[object_type][name]
else: else:
logger.error(f"Unknown Template requested: {name} in {object_type.name}") logger.warning(f"Unknown Template requested: {name} in {object_type.name}")
sys.exit(1) return SnowrunnerClass(pak_loader=self)
def get_object( def get_object(
self, object_type: SnowrunnerGameObjectType, name: str, lazy: bool = False self, object_type: SnowrunnerGameObjectType, name: str, lazy: bool = False
) -> SnowrunnerClass | None: ) -> SnowrunnerClass | None:
FAIL_NAMES = ["CargoBarrelsMasut", "CargoServiceSparePartsTown", "LongLogs", "MediumLogs", "ShortLogs"]
# name = name.lower() # name = name.lower()
if object_type in self.game_data: if object_type in self.game_data:
if name in self.game_data[object_type]: if name in self.game_data[object_type]:
@ -1912,7 +1973,7 @@ class InitialPakLoader:
if getattr(v, "Name", "<UNKNOWN>") == name: if getattr(v, "Name", "<UNKNOWN>") == name:
return v return v
if lazy: if lazy or name in FAIL_NAMES:
return None return None
logger.error(f"Unknown Game Data Object requested: {name} in {object_type.name}") logger.error(f"Unknown Game Data Object requested: {name} in {object_type.name}")
@ -1921,6 +1982,9 @@ class InitialPakLoader:
def get_object_list(self, object_type: SnowrunnerGameObjectType) -> list: def get_object_list(self, object_type: SnowrunnerGameObjectType) -> list:
return list(self.game_data[object_type].values()) return list(self.game_data[object_type].values())
def get_object_list_with_ref(self, object_type: SnowrunnerGameObjectType) -> list[tuple[str, SnowrunnerClass]]:
return list(self.game_data[object_type].items())
def get_sorted_object_list( def get_sorted_object_list(
self, object_type: SnowrunnerGameObjectType, language: LanguageLoader self, object_type: SnowrunnerGameObjectType, language: LanguageLoader
) -> list[tuple[str, SnowrunnerClass]]: ) -> list[tuple[str, SnowrunnerClass]]:

128
output.py
View File

@ -6,8 +6,6 @@ from loader import (
LanguageLoader, LanguageLoader,
SnowrunnerGameObjectType, SnowrunnerGameObjectType,
TruckAddonCategory, TruckAddonCategory,
GameData,
SnowrunnerClass,
) )
from logging import getLogger from logging import getLogger
@ -32,6 +30,8 @@ def get_head(language: LanguageLoader, category: str, name: str | None = None) -
<ul id='navi_cat'> <ul id='navi_cat'>
<li><a href="/{language.Name}/trucks/">Trucks</a></li> <li><a href="/{language.Name}/trucks/">Trucks</a></li>
<li><a href="/{language.Name}/frame_addons/">Frame Addons</a></li> <li><a href="/{language.Name}/frame_addons/">Frame Addons</a></li>
<li><a href="/{language.Name}/trailers/">Trailers</a></li>
<li><a href="/{language.Name}/cargo_types/">Cargo Types</a></li>
</ul> </ul>
</div> </div>
<div id='content'> <div id='content'>
@ -423,7 +423,7 @@ def write_frame_addons(pak: InitialPakLoader, destination_directory: str, langua
available_trucks = [ available_trucks = [
x x
for x in pak.get_sorted_object_list(SnowrunnerGameObjectType.TRUCK, language=language) 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 x[1].Type == "Truck" and frame_addon.GameData.InstallSocket.Type in x[1].get_addon_socket_names()
] ]
if not available_trucks: if not available_trucks:
continue continue
@ -459,6 +459,118 @@ def write_frame_addons(pak: InitialPakLoader, destination_directory: str, langua
) )
def write_trailers(pak: InitialPakLoader, destination_directory: str, language: LanguageLoader):
index_content = " <h1>Trailers</h1>\n"
index_content += f" <table id='trailers' class='display'>\n"
index_content += (
f" <thead><tr>\n"
f" <th>Name</th>\n"
f" <th>Price</th>\n"
f" <th>Unlock By</th>\n"
f" <th>Quest</th>\n"
f" <th>Mass</th>\n"
f" <th>Hitch</th>\n"
f" <th>Cargo Slots</th>\n"
f" <th>Repair Points</th>\n"
f" <th>Spare Wheels</th>\n"
f" <th>Fuel Capacity</th>\n"
f" <th>Water Capacity</th>\n"
f" </tr></thead>\n"
)
index_content += f" <tbody>\n"
for ref, trailer in pak.get_sorted_object_list(SnowrunnerGameObjectType.TRUCK, language=language):
if trailer.Type != "Trailer":
continue
index_content += f" <tr>\n"
index_content += f" <td>{trailer.get_translated_ui_name(language)}</td>\n"
index_content += f" <td>{trailer.Price}</td>\n"
index_content += f" <td>{trailer.GameData.get_unlock_str()}</td>\n"
index_content += f" <td>{trailer.GameData.IsQuest}</td>\n"
index_content += f" <td>{trailer.PhysicsModel.Weight}kg</td>\n"
index_content += f" <td>{trailer.GameData.InstallSocket.Type}</td>\n"
if trailer.GameData.AddonSlots is None:
index_content += f" <td>&nbsp;</td>\n"
else:
if isinstance(trailer.GameData.AddonSlots, list):
index_content += f" <td>{sum(x.Quantity for x in trailer.GameData.AddonSlots)}</td>\n"
else:
index_content += f" <td>{trailer.GameData.AddonSlots.Quantity}</td>\n"
index_content += f" <td>{trailer.TruckData.RepairsCapacity if trailer.TruckData is not None and trailer.TruckData.RepairsCapacity > 0 else '&nbsp;'}</td>\n"
index_content += f" <td>{trailer.TruckData.WheelRepairsCapacity if trailer.TruckData is not None and trailer.TruckData.WheelRepairsCapacity > 0 else '&nbsp;'}</td>\n"
index_content += f" <td>{trailer.TruckData.FuelCapacity if trailer.TruckData is not None and trailer.TruckData.FuelCapacity > 0 else '&nbsp;'}</td>\n"
index_content += f" <td>{trailer.TruckData.WaterCapacity if trailer.TruckData is not None and trailer.TruckData.WaterCapacity > 0 else '&nbsp;'}</td>\n"
index_content += f" </tr>\n"
index_content += f" </tbody>\n"
index_content += f" </table>\n"
index_content += " <script>document.addEventListener('DOMContentLoaded', function() {new DataTable('#trailers', {paging: false});});</script>\n"
write_html(
os.path.join(destination_directory, "index.html"),
language=language,
content=index_content,
category="Trailers",
)
def write_cargo_types(pak: InitialPakLoader, destination_directory: str, language: LanguageLoader):
index_content = " <h1>Trailers</h1>\n"
index_content += f" <table id='cargo_types' class='display'>\n"
index_content += (
f" <thead><tr>\n"
f" <th>Name</th>\n"
f" <th>Mass (unpacked)</th>\n"
f" <th>Mass (packed)</th>\n"
f" <th>Cargo Slots</th>\n"
f" </tr></thead>\n"
)
index_content += f" <tbody>\n"
for _, cargo_type in pak.get_sorted_object_list(SnowrunnerGameObjectType.CARGO_TYPE, language=language):
install_addons = [
x
for x in pak.get_object_list(SnowrunnerGameObjectType.TRUCK_ADDON)
if x.GameData.InstallSlot is not None and x.GameData.InstallSlot.CargoType == cargo_type
]
if not install_addons:
continue
if len(install_addons) > 1:
# FIXME: These are Logs
continue
install_addon = install_addons[0]
if install_addon.PhysicsModel is None:
continue # FIXME: Water, Fuel
model_brands = [
x
for x in pak.get_object_list(SnowrunnerGameObjectType.MODEL_BRAND)
if x.GameData is not None and x.GameData.LoadType == cargo_type
]
model_brand = model_brands[-1]
index_content += f" <tr>\n"
index_content += f" <td>{cargo_type.get_translated_ui_name(language)}</td>\n"
index_content += f" <td>{model_brand.PhysicsModel.Weight}</td>\n"
index_content += f" <td>{install_addon.PhysicsModel.Weight}</td>\n"
index_content += f" <td>{install_addon.GameData.InstallSlot.CargoLength}</td>\n"
index_content += f" </tr>\n"
index_content += f" </tbody>\n"
index_content += f" </table>\n"
index_content += " <script>document.addEventListener('DOMContentLoaded', function() {new DataTable('#cargo_types', {paging: false});});</script>\n"
write_html(
os.path.join(destination_directory, "index.html"),
language=language,
content=index_content,
category="Cargo Types",
)
def create_html(pak: InitialPakLoader, destination_directory: str = "output/"): def create_html(pak: InitialPakLoader, destination_directory: str = "output/"):
if not os.path.exists(destination_directory): if not os.path.exists(destination_directory):
os.mkdir(destination_directory) os.mkdir(destination_directory)
@ -479,3 +591,13 @@ def create_html(pak: InitialPakLoader, destination_directory: str = "output/"):
if not os.path.exists(frame_addon_path): if not os.path.exists(frame_addon_path):
os.mkdir(frame_addon_path) os.mkdir(frame_addon_path)
write_frame_addons(pak=pak, destination_directory=frame_addon_path, language=language_data) write_frame_addons(pak=pak, destination_directory=frame_addon_path, language=language_data)
trailers_path = os.path.join(destination_directory, language_name, "trailers")
if not os.path.exists(trailers_path):
os.mkdir(trailers_path)
write_trailers(pak=pak, destination_directory=trailers_path, language=language_data)
cargo_types_path = os.path.join(destination_directory, language_name, "cargo_types")
if not os.path.exists(cargo_types_path):
os.mkdir(cargo_types_path)
write_cargo_types(pak=pak, destination_directory=cargo_types_path, language=language_data)