Transport Fever 2

Transport Fever 2

Smart Capacity Customizer for Vehicles
Ims 10 月 27 日 上午 9:03
Broken MU vehicle type check
最后由 Ims 编辑于; 10 月 27 日 下午 10:26
< >
正在显示第 1 - 7 条,共 7 条留言
Ims 10 月 27 日 下午 10:14 
Alright. I believe I have reached a conclusion.

Issue
  1. MU check always fails due to 2 major bugs.
  2. MUs may still identify as passenger wagons
  3. Otherwise get the fallback cargo multiplier.

Four distinct issues identified:
  1. Line 146: `carrier == "RAIL"` → should be `carrier == 1` (numeric, not string). Always fails and blocks everything.
  2. Line 146: `and data.metadata.id` also blocks everything because `id` is always nil at runtime. Remove from guard condition and make `multipleUnitRep` lookup conditional.
  3. Line 178: Remove `and Utils.checkDrivingLicense(data, 2)` as both redundant and incorrectly disqualifying some modded units that lack seatProvider (and therefore no drivingLicense field) but still have multipleUnitOnly = true.
  4. Line 213: Fallback uses cargo multiplier without positively identifying cargo wagons. If for whatever reason a vehicle fails the check for its type and all others, it gets arbitrarily assigned the cargo multiplier. Prefer a positive cargo wagon check. If all checks fail, prefer no multiplier applied, and log a warning message.

Problems reported for three mods

Result of failed checks
  • Pendolino: All units except the front received cargo multiplier. Front unit responds to passenger multiplier.
  • JRE 253: The middle unit (moha253.mdl) received the cargo multiplier. Others not specifically checked.
  • CRH6A: The whole thing is given the cargo multiplier.

As mentioned, units that don't get the cargo multiplier will have passed the passenger wagon check instead. The Pendolino (short) has normal capacity 188 with all set to 1x, With passenger wagon set to 2x, capacity is 235. Of 4 units à 47 passengers, one gets doubled (the front unit). 5x47=235. With cargo wagons set to 2x, all others 1x, the reverse happens: All units are doubled except one, the front unit.

Again, please see Reddit thread for more details.

You could argue that these mods are somewhat misconfigured, but this only accounts for a minor aspect of why they failed to be recognized as MUs. Two of the issues listed above result in the MU check always failing. The lack of seatProvider or drivingLicense == 2 is not even a factor; you have to get past the other two first.

I don't see why you would use the drivingLicense field to judge if something is an MU. This field tells the game what type of crew to put in the cab[wiki.transportfever2.com]. So it's essentially another check to see if the vehicle is a rail vehicle. But you're already doing that by checking the carrier (once that is done correctly). Therefore I believe this check is redundant. Plus it's an obstacle for, let's say, lazily configured mods, such as the CRH6A, which doesn't have a driver. It's otherwise fine; it should not be disqualified on that basis.

Maybe there's some aspect I'm not seeing, in which case I'm happy to be corrected.

If multipleUnitOnly is true, that should be enough on its own to qualify as MU. So this should not be and-ed with additional conditions. The difficulty is when multipleUnitOnly is false, because it might still be MU, but one where the individual units can be purchased separately (case in point: doug's unlocked MUs).

I don't currently have a good suggestion for this, except to say that failing on this point is in most cases much less severe than the current situation. If nothing else, most units would still pass as passenger wagons, so you could use that multiplier, which I reckon most users would already be using in that situation. I'll see tomorrow if I can come up with something.

Also no specific proposal for #4.

mod.lua with proposed fixes and associated comments[pastebin.com]

With these fixes, the MU check works, and both vanilla and the modded EMUs listed have the MU capacity multiplier correctly applied.
最后由 Ims 编辑于; 10 月 27 日 下午 10:15
AksLrs  [开发者] 10 月 28 日 上午 11:48 
=========================
Important note
--------------------
Due to limited time on my side I haven’t been able to do an in-game validation pass on the code I provide. Can you please run this version (at the next reply) in your setup and confirm that it behaves as expected across those consists? Your confirmation would be super valuable for finalizing this release, and make it public.
=========================

First of all, thank you for the detailed write-up and for taking the time to break down the issues with real examples (Pendolino / JRE 253 / CRH6A). That kind of feedback is better because it doesn’t just say “it’s wrong”, it actually explains why it’s wrong.

These mods you mentioned, are not follow TF2 good modding practice.

Based on your report I’ve done a fairly deep refactor of the script logic. The new version is 2.6. The goal wasn’t only to “patch the issues”, but to make the classification system more explicit, more robust against mod variance, and easier to extend later.

Here’s what changed, mapped to the issues you raised:

MU detection was basically broken

You were 100% right about carrier == "RAIL" being compared as a string and about the and data.metadata.id guard. That combination basically meant MU identification would silently fail for a lot of rolling stock, especially middle cars.

In 2.6 we now normalize carrier codes through a Constants.CARRIER enum and a classifyCarrier() helper, so we’re checking numeric carrier values (and metadata.roadVehicle as a fallback for road vehicles).

MU identification no longer hard-requires metadata.id. The id is now only used for the multipleUnitRep lookup if it exists. Lack of id no longer blocks MU classification.

MU classification and drivingLicense

You pointed out that using drivingLicense to “prove” that a vehicle is an MU is both wrong and harmful (a lot of modded EMUs/DMUs don’t even provide a driver seat or a proper seatProvider/drivingLicense on every car). It is a long ago to write the script, but if I remeber well, I think that the perpose of this "hack" with seatProvider/drivingLicense, was to get also some other "bas modding practice" mods, and make them work with this script.

Anyway, I removed that requirement completely. drivingLicense is no longer part of MU detection.

MU is now considered true if ANY of these positive signals holds:

The model’s id is present in multipleUnitRep.

The model declares transportVehicle.multipleUnitOnly == true.

The model heuristically looks like a powered cab car (has crew seats and engines).

This matches what you described: “if multipleUnitOnly is true, that should be enough on its own”, and also covers unlocked/loosely configured sets where not every intermediate car has a cab.

Tram / light rail handling

I now treat trams separately via isTram(), which basically says: “you’re a rail vehicle and you expose a tram-style driving license”. This keeps trams under their own multiplier, instead of trying to force them into MU or wagon buckets.

Importantly, tram logic does NOT interfere with MU logic anymore.

Passenger vs cargo vs fallback

You correctly pointed out that the old fallback was dangerous: anything that failed all the previous checks would get the cargo wagon multiplier by default. That’s why in sets like the Pendolino you’d see middle cars get treated like freight wagons.

That’s gone.

Now we only assign the cargo multiplier if we can positively identify that we are:

on rail,

not MU,

not tram,

and the vehicle does NOT carry passengers.
In other words, “cargo wagon” has to be proven by exclusion inside rail context.

If a vehicle cannot be confidently classified into any known role, we no longer slap “cargo” on it. We simply do not apply any capacity multiplier (i.e. it stays at 1x). We also emit a one-time diagnostic warning in the log so mod authors can see that classification failed for something.

This is exactly in line with what you suggested (“prefer a positive cargo wagon check; if all checks fail, prefer no multiplier applied”).

Role-based classification layer

Instead of a long if/elseif chain scattered everywhere, there’s now a dedicated classification pipeline:

First I normalize the “carrier class” (rail, road, air, water, unknown).

Then I derive a semantic “vehicle role”:
rail_mu, rail_tram, rail_passenger, rail_cargo,
road_passenger, road_cargo,
air_passenger, air_cargo,
water_generic,
or unclassified.

Finally I resolve that role to the correct multiplier from the user sliders.

This means:

Rail MU cars (including EMUs/DMUs where middle cars don’t have their own cab or license) now reliably get the MU multiplier.

Rail passenger coaches get the wagon passenger multiplier.

Rail wagons that are truly freight get the cargo multiplier.

Road/air/water vehicles get the proper bucket.

Anything weird / partially defined just stays at 1x.

I also memoize classification per modelData so we’re not recomputing it pointlessly, and I added a throttled logger (logOnce) so if something falls through into “unclassified” you’ll get a single diagnostic line instead of console spam.

Pricing / running cost scaling

The running cost divider and purchase price divider now apply only if the vehicle is classified as rail, which matches the tooltip and intent (“Reduce Price & Running Cost for all rail vehicles”).

I keep resetting runningCosts / price to -1 only for dual-mode rail+road stuff in runFn, same as before. If you want to normalize all rail upfront instead, you can drop the and data.metadata.roadVehicle condition in that modifier.

Safer defaults

All sliders now have defaultIndex pointing to the actual 1x entry (“Default”), so the mod is no longer secretly applying x5 or something right out of the box.

Capacity changes are still clamped with math.max(1, ...) so I don’t generate 0-capacity vehicles that break the game economy.

Bottom line:

MU detection is now resilient to missing id, missing seatProvider, and missing drivingLicense.

Passenger vs cargo assignment is now explicit, not “best effort, else cargo”.

Sets like Pendolino / JR EAST 253 / CRH6A should get consistent multipliers across all cars instead of random cars being treated as cargo wagons (I don't check their code, but I'm sure the code is not following TF good modding practices).

If something can’t be classified confidently, I don’t mutate its capacity at all.

I’ve included the new version (2.6) in full so you and anyone else interested can review it, test it with those specific rolling stock packs, and confirm that the behavior is now correct for:

leading cars,

intermediate cars,

cargo-only wagons,

and mixed/”lazy” modded units.
AksLrs  [开发者] 10 月 28 日 上午 11:49 
-- Smart Capacity Customizer for Vehicles
-- Major Version 2.6 - 28/10/2025
--
-- Philosophy
-- - Preserve runtime behavior and balancing logic of v2.5 (no gameplay change).
-- - Introduce hardened classification routines with memoization.
-- - Express intent with high-level semantic helpers, so future contributors
-- can reason about the code without reverse-engineering nested ifs.
--
-- tl;dr: same multipliers, smarter brain.

function data()

----------------------------------------------------------------
-- CONSTANT DEFINITIONS / STATIC CONFIGURATION
----------------------------------------------------------------
local Constants = {
PASSENGER_TYPE = "PASSENGERS",

-- Transport Fever 2 carrier "enums". Adjust if upstream changes.
CARRIER = {
RAIL = 1,
ROAD = 2,
AIR = 3,
WATER = 4
},

LICENSE = {
TRAIN = 2,
TRAM = 3
},

passengerMultipliers = {
0.125, 0.25, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4,
4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9,
9.5, 10, 12, 14, 16, 18, 20
},

cargoMultipliers = {
0.125, 0.25, 0.5, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 12, 14, 16, 18, 20, 30, 40
},

priceRunningCost = {
1,
0.05,
0.05555555555555555,
0.0625,
0.07142857142857142,
0.08333333333333333,
0.1,
0.1111111111111111,
0.125,
0.14285714285714285,
0.16666666666666666,
0.2,
0.25,
0.3333333333333333,
0.5
}
}

----------------------------------------------------------------
-- RUNTIME UTILITIES / STATEFUL HELPERS
----------------------------------------------------------------
local Utils = {}

-- Internal memoization tables:
-- We memoize classification results per modelData reference.
-- Keyed directly by table reference because modelData is stable per loop.
local _memo = {
carrierClass = setmetatable({}, { __mode = "k" }), -- weak keys: don't leak
vehicleRole = setmetatable({}, { __mode = "k" }),
warned = {} -- non-weak; tiny footprint of strings
}

----------------------------------------------------------------
-- Safe getter for nested tables.
-- Usage: Utils.safeGet(obj, "metadata","transportVehicle","carrier")
----------------------------------------------------------------
function Utils.safeGet(root, ...)
local result = root
for _, key in ipairs({...}) do
result = result and result[key]
if not result then break end
end
return result
end

----------------------------------------------------------------
-- DIAGNOSTICS
-- logOnce(category, message):
-- Print a warning message ONCE per category string. Prevents spam.
----------------------------------------------------------------
local function logOnce(category, msg)
if not _memo.warned[category] then
_memo.warned[category] = true
print(("[SmartCapacity][%s] %s"):format(category, msg or ""))
end
end

----------------------------------------------------------------
-- CLASSIFICATION LAYER
--
-- We do two conceptual classifications:
--
-- 1. carrierClass(modelData) -> "rail" | "road" | "air" | "water" | "unknown"
-- This is based on metadata.transportVehicle.carrier and metadata.roadVehicle.
--
-- 2. vehicleRole(modelData, multipleUnitRep) -> semantic role:
-- "rail_mu", "rail_tram", "rail_passenger", "rail_cargo",
-- "road_passenger", "road_cargo",
-- "air_passenger", "air_cargo",
-- "water_generic",
-- "unclassified"
--
-- This two-level reasoning is way easier to extend and reason about than
-- spaghetti if/else spread everywhere.
----------------------------------------------------------------

-- Extract numeric carrier code
local function getCarrierCode(data)
return Utils.safeGet(data, "metadata", "transportVehicle", "carrier")
end

-- Determine "carrier class" string in a normalized way.
local function classifyCarrier(data)
-- memoized?
local cached = _memo.carrierClass[data]
if cached then return cached end

local carrierCode = getCarrierCode(data)
local carrierGuess = "unknown"

repeat
-- Strict numeric carrier check
if carrierCode == Constants.CARRIER.RAIL then
carrierGuess = "rail"
break
end
if carrierCode == Constants.CARRIER.ROAD then
carrierGuess = "road"
break
end
if carrierCode == Constants.CARRIER.AIR then
carrierGuess = "air"
break
end
if carrierCode == Constants.CARRIER.WATER then
carrierGuess = "water"
break
end

-- Heuristic: some road vehicles might rely on metadata.roadVehicle rather than carrier enum.
if Utils.safeGet(data, "metadata", "roadVehicle") ~= nil then
carrierGuess = "road"
break
end

-- else remains "unknown"
until true

_memo.carrierClass[data] = carrierGuess
return carrierGuess
end

-- Lightweight helpers built on classifyCarrier:
function Utils.isRail(data) return classifyCarrier(data) == "rail" end
function Utils.isRoad(data) return classifyCarrier(data) == "road" end
function Utils.isAir(data) return classifyCarrier(data) == "air" end
function Utils.isWater(data) return classifyCarrier(data) == "water" end

----------------------------------------------------------------
-- Driving license hint (not authoritative)
----------------------------------------------------------------
function Utils.checkDrivingLicense(data, licenseType)
local dl = Utils.safeGet(data, "metadata", "seatProvider", "drivingLicense")
return dl ~= nil and dl == licenseType
end

----------------------------------------------------------------
-- isTram:
-- Heuristic tram: rail vehicle + tram-style driving license.
-- We intentionally do NOT require this for MU classification.
----------------------------------------------------------------
function Utils.isTram(data)
if not Utils.isRail(data) then return false end
if Utils.checkDrivingLicense(data, Constants.LICENSE.TRAM) then
return true
end
return false
end

----------------------------------------------------------------
-- hasCrewSeatAndEngines:
-- Used as a looser MU heuristic for powered cab cars.
----------------------------------------------------------------
function Utils.hasCrewSeatAndEngines(data)
if not data or not data.metadata or not data.metadata.transportVehicle then
return false
end
local tv = data.metadata.transportVehicle
local seatProvider = data.metadata.seatProvider

if not seatProvider or not seatProvider.seats or #seatProvider.seats == 0 then
return false
end
if not tv.engines or #tv.engines == 0 then
return false
end

return true
end

----------------------------------------------------------------
-- carriesPassengers:
-- Positive passenger detection (any layer, any config).
----------------------------------------------------------------
local function carriesPassengers(data)
local tv = Utils.safeGet(data, "metadata", "transportVehicle")
if not tv then return false end

local function loadConfigHasPassengers(loadConfigs)
for _, loadConfig in ipairs(loadConfigs or {}) do
for _, cargoEntry in ipairs(loadConfig.cargoEntries or {}) do
if cargoEntry.type == Constants.PASSENGER_TYPE then
return true
end
end
end
return false
end

-- direct passenger flag
if tv.type == Constants.PASSENGER_TYPE then
return true
end

-- loadConfigs on vehicle
if loadConfigHasPassengers(tv.loadConfigs) then
return true
end

local function containerHasPassengers(container)
for _, item in ipairs(container or {}) do
if item.type == Constants.PASSENGER_TYPE then
return true
end
if loadConfigHasPassengers(item.loadConfigs) then
return true
end
end
return false
end

if containerHasPassengers(tv.compartmentsList) then return true end
if containerHasPassengers(tv.compartments) then return true end
if containerHasPassengers(tv.capacities) then return true end

return false
end
AksLrs  [开发者] 10 月 28 日 上午 11:50 
----------------------------------------------------------------
-- isMultipleUnit:
-- Unified MU logic (robust against mod variance).
--
-- Conditions considered SUFFICIENT (logical OR):
-- - rail carrier AND:
-- * model has id in multipleUnitRep
-- OR* model declares multipleUnitOnly = true
-- OR* model heuristically looks like powered cab with crew seats
--
-- Absolutely NO requirement for drivingLicense.
-- Absolutely NO hard requirement for metadata.id to exist.
----------------------------------------------------------------
local function isMultipleUnit(data, multipleUnitRep)
if not Utils.isRail(data) then
return false
end

local tv = Utils.safeGet(data, "metadata", "transportVehicle")
local id = Utils.safeGet(data, "metadata", "id")

-- strongest: explicitly known MU consist
if id and multipleUnitRep and multipleUnitRep[id] ~= nil then
return true
end

-- explicit MU-only flag
if tv and tv.multipleUnitOnly then
return true
end

-- powered cab fallback
if Utils.hasCrewSeatAndEngines(data) then
return true
end

return false
end

----------------------------------------------------------------
-- classifyVehicleRole(data, multipleUnitRep):
-- Returns a semantic high-level role string.
--
-- rail_mu : multiple unit rolling stock (EMU/DMU etc.)
-- rail_tram : tram / streetcar like light rail
-- rail_passenger : rail passenger coach/wagon
-- rail_cargo : rail freight wagon
-- road_passenger : buses, coaches, etc.
-- road_cargo : trucks, vans, etc.
-- air_passenger : passenger aircraft
-- air_cargo : cargo aircraft
-- water_generic : boats/ships (all treated same for capacity mult)
-- unclassified : fallback (no multiplier change)
--
-- Memoized because classification may be referenced multiple times.
----------------------------------------------------------------
local function classifyVehicleRole(data, multipleUnitRep)
local cached = _memo.vehicleRole[data]
if cached then return cached end

local role = "unclassified"
local carrier = classifyCarrier(data)
local pax = carriesPassengers(data)

repeat
if carrier == "rail" then
if isMultipleUnit(data, multipleUnitRep) then
role = "rail_mu"
break
end

if Utils.isTram(data) then
role = "rail_tram"
break
end

if pax then
role = "rail_passenger"
break
end

-- rail but not MU/tram and no passengers = freight wagon
role = "rail_cargo"
break
end

if carrier == "road" then
role = pax and "road_passenger" or "road_cargo"
break
end

if carrier == "air" then
role = pax and "air_passenger" or "air_cargo"
break
end

if carrier == "water" then
role = "water_generic"
break
end

-- else: remains "unclassified"
until true

-- Optional diagnostic for weird mods:
if role == "unclassified" then
logOnce(
"unclassified_vehicle",
"Could not classify vehicle role; capacity multiplier left as default (1x)."
)
end

_memo.vehicleRole[data] = role
return role
end

----------------------------------------------------------------
-- NUMERIC MULTIPLIER RESOLUTION
-- Given vehicleRole and multiplierCache from mod params,
-- return the appropriate multiplier (or nil if none).
----------------------------------------------------------------
local function resolveMultiplierForRole(vehicleRole, multiplierCache)
local map = {
rail_mu = multiplierCache.mu,
rail_tram = multiplierCache.tramMu,
rail_passenger = multiplierCache.wagonPassenger,
rail_cargo = multiplierCache.cargo,
road_passenger = multiplierCache.roadPassenger,
road_cargo = multiplierCache.roadCargo,
air_passenger = multiplierCache.passengerAir,
air_cargo = multiplierCache.airCargo,
water_generic = multiplierCache.waterMu,
unclassified = nil
}
return map[vehicleRole]
end

----------------------------------------------------------------
-- CAPACITY MUTATION
-- Applies a single numeric multiplier to all capacity surfaces in a model.
----------------------------------------------------------------
function Utils.modifyCargoEntries(data, multiplier)
local function modifyCapacity(container)
local capacity = Utils.safeGet(container, "capacity")
if capacity then
-- clamp floor at 1 seat/unit to avoid zero-capacity breakage
container.capacity = math.max(1, math.floor(capacity * multiplier))
end
end

local function modifyLoadConfigs(loadConfigs)
for _, loadConfig in ipairs(loadConfigs or {}) do
for _, cargoEntry in ipairs(Utils.safeGet(loadConfig, "cargoEntries") or {}) do
cargoEntry.capacity = math.max(1, math.floor(cargoEntry.capacity * multiplier))
end
end
end

local tv = Utils.safeGet(data, "metadata", "transportVehicle")
if not tv then return end

-- top level
modifyCapacity(tv)

-- compartments
if tv.compartments then
for _, compartment in ipairs(tv.compartments) do
modifyCapacity(compartment)
modifyLoadConfigs(compartment.loadConfigs)
modifyLoadConfigs(compartment.cargoEntries)
end
end

-- compartmentsList
if tv.compartmentsList then
for _, compList in ipairs(tv.compartmentsList) do
modifyCapacity(compList)
modifyLoadConfigs(compList.loadConfigs)
modifyLoadConfigs(compList.cargoEntries)
end
end

-- capacities
if tv.capacities then
for _, cap in ipairs(tv.capacities) do
modifyCapacity(cap)
end
end
end

function Utils.mathRound(num)
return num >= 0 and math.floor(num + 0.5) or math.ceil(num - 0.5)
end

----------------------------------------------------------------
-- PUBLIC API: determineMultiplier
-- High-level wrapper that external code calls.
-- Uses the role classification pipeline and resolves the right multiplier.
--
-- Return:
-- number multiplier
-- or nil if "unclassified" (caller should default to 1.0)
----------------------------------------------------------------
function Utils.determineMultiplier(data, multiplierCache, multipleUnitRep)
-- classify role semantically:
local role = classifyVehicleRole(data, multipleUnitRep)

-- map semantic role -> concrete numeric multiplier:
local m = resolveMultiplierForRole(role, multiplierCache)

-- if m == nil, caller will choose multiplier = 1 (no change)
return m
end
AksLrs  [开发者] 10 月 28 日 上午 11:50 
----------------------------------------------------------------
-- MOD DECLARATION
----------------------------------------------------------------
return {
info = {
majorVersion = 2,
minorVersion = 6,
severityAdd = "NONE",
severityRemove = "NONE",
name = _("Smart Capacity Customizer for Vehicles"),
description = _([[ Enhance your gameplay by dynamically adjusting in a smart and context-aware way the capacity of all vehicles.

This mod allows for precise customization of passenger and cargo capacities for most vehicles and lets you tweak those vehicles to perfectly suit your gameplay style, from enhancing realism to maximizing efficiency.

Compatible with vanilla and with most vehicle mods (if they follow TF2 good modding practice).

This version introduces a more robust classification layer for vehicles (rail MU, tram, wagon, cargo wagon, road pax/cargo, air pax/cargo, water, etc.) and applies multipliers only after positive identification. If we cannot confidently classify a vehicle, we leave it at 1x (no forced cargo multiplier). The logic has built-in diagnostics to help mod authors understand why something was not recognized.

Sliders let you pick multipliers from 1/8 up to x20 (x40 for cargo wagons). "Default" means 1x, i.e. no change from original capacity.

Writing code is a complex and time-consuming task, which often takes me away from my work and family. If you appreciate my work and would like to support me, so that I can continue to help all of you in the future, please consider clicking on the link below:
https://buy.stripe.com/fZe9C2c2u5np67m289

Safe to add/remove in your save game. If you enjoy it, give it a thumb up :)

[img]https://i.imgur.com/fVVaDCS.gif[/img] ]]),
visible = true,
tags = { "Script Mod", "Vehicle", "Misc", "Capacity", "Wagon", "Multiple Unit", "Tram", "Cargo", "Bus", "Road", "Train" },
authors = {
{
name = "Aks_Lrs",
role = "CREATOR",
text = "",
},
},
params = {
{
key = "muPassengerMultiplier",
name = _("Multiple Unit Passenger Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x1.5"), _("x2"), _("x2.5"), _("x3"), _("x3.5"), _("x4"), _("x4.5"), _("x5"), _("x5.5"), _("x6"), _("x6.5"), _("x7"), _("x7.5"), _("x8"), _("x8.5"), _("x9"), _("x9.5"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20")},
defaultIndex = 3, -- "Default" = multiplier 1
tooltip = _("Adjust the passenger capacity multiplier for multiple unit rail vehicles."),
},
{
key = "wagonPassengerMultiplier",
name = _("Passenger Wagon Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x1.5"), _("x2"), _("x2.5"), _("x3"), _("x3.5"), _("x4"), _("x4.5"), _("x5"), _("x5.5"), _("x6"), _("x6.5"), _("x7"), _("x7.5"), _("x8"), _("x8.5"), _("x9"), _("x9.5"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20")},
defaultIndex = 3,
tooltip = _("Adjust the passenger capacity multiplier for wagons."),
},
{
key = "cargoWagonMultiplier",
name = _("Cargo Wagon Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x2"), _("x3"), _("x4"), _("x5"), _("x6"), _("x7"), _("x8"), _("x9"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20"), _("x30"), _("x40")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for cargo wagons."),
},
{
key = "tramMultiplier",
name = _("Tram Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x1.5"), _("x2"), _("x2.5"), _("x3"), _("x3.5"), _("x4"), _("x4.5"), _("x5"), _("x5.5"), _("x6"), _("x6.5"), _("x7"), _("x7.5"), _("x8"), _("x8.5"), _("x9"), _("x9.5"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for Trams."),
},
{
key = "roadPassengerMultiplier",
name = _("Road Passenger Vehicle Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x1.5"), _("x2"), _("x2.5"), _("x3"), _("x3.5"), _("x4"), _("x4.5"), _("x5"), _("x5.5"), _("x6"), _("x6.5"), _("x7"), _("x7.5"), _("x8"), _("x8.5"), _("x9"), _("x9.5"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for road passenger vehicles."),
},
{
key = "roadCargoMultiplier",
name = _("Road Cargo Vehicle Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x2"), _("x3"), _("x4"), _("x5"), _("x6"), _("x7"), _("x8"), _("x9"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20"), _("x30"), _("x40")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for road cargo vehicles."),
},
{
key = "passengerAirMultiplier",
name = _("Passenger Air Vehicle Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x1.5"), _("x2"), _("x2.5"), _("x3"), _("x3.5"), _("x4"), _("x4.5"), _("x5"), _("x5.5"), _("x6"), _("x6.5"), _("x7"), _("x7.5"), _("x8"), _("x8.5"), _("x9"), _("x9.5"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for Passenger air vehicles."),
},
{
key = "cargoAirMultipliers",
name = _("Cargo Air Vehicle Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x2"), _("x3"), _("x4"), _("x5"), _("x6"), _("x7"), _("x8"), _("x9"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20"), _("x30"), _("x40")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for Cargo Air Vehicle."),
},
{
key = "waterMultiplier",
name = _("Water Vehicle Capacity Multiplier"),
uiType = "SLIDER",
values = {_("1/8"), _("1/4"), _("1/2"), _("Default"), _("x1.5"), _("x2"), _("x2.5"), _("x3"), _("x3.5"), _("x4"), _("x4.5"), _("x5"), _("x5.5"), _("x6"), _("x6.5"), _("x7"), _("x7.5"), _("x8"), _("x8.5"), _("x9"), _("x9.5"), _("x10"), _("x12"), _("x14"), _("x16"), _("x18"), _("x20")},
defaultIndex = 3,
tooltip = _("Adjust the capacity multiplier for Water Vehicle."),
},
{
key = "priceRunningCost",
name = _("Price & Running Cost Divider"),
uiType = "SLIDER",
values = {_("Default"), _("1/20"), _("1/18"), _("1/16"), _("1/14"), _("1/12"), _("1/10"), _("1/9"), _("1/8"), _("1/7"), _("1/6"), _("1/5"), _("1/4"), _("1/3"), _("1/2")},
defaultIndex = 0, -- "Default" = 1.0
tooltip = _("Reduce Price & Running Cost for all rail vehicles."),
},
},
},

----------------------------------------------------------------
-- runFn:
-- We reset cost fields (-1) ONLY for dual-mode rail+road vehicles,
-- so the later scaling has clean inputs.
-- If you want to normalize ALL rail vehicles instead, drop the "and data.metadata.roadVehicle" gate.
----------------------------------------------------------------
runFn = function(settings)
local function adjustRailVehicleModel(fileName, data)
if data.metadata
and data.metadata.railVehicle
and data.metadata.roadVehicle
and data.metadata.transportVehicle
and data.metadata.maintenance
and data.metadata.cost
then
data.metadata.maintenance.runningCosts = -1
data.metadata.cost.price = -1
end
return data
end

addModifier("loadModel", adjustRailVehicleModel)
end,

----------------------------------------------------------------
-- postRunFn:
-- 1. Build multiplierCache from mod params.
-- 2. Iterate all vehicles.
-- 3. Classify & resolve multiplier.
-- 4. Apply capacity scaling.
-- 5. Apply running cost scaling ONLY on rail (per tooltip).
--
-- NOTE: We memoize classification to look fancy *and* avoid repeat work.
----------------------------------------------------------------
postRunFn = function(settings, modParams)
local allVehicles = api.res.modelRep.getAll()
local multipleUnitRep = api.res.multipleUnitRep.getAll()

local params = modParams[getCurrentModId()]
if not params then
return false
end

local multiplierCache = {
mu = Constants.passengerMultipliers[params.muPassengerMultiplier + 1],
wagonPassenger = Constants.passengerMultipliers[params.wagonPassengerMultiplier + 1],
cargo = Constants.cargoMultipliers[params.cargoWagonMultiplier + 1],
tramMu = Constants.passengerMultipliers[params.tramMultiplier + 1],
roadPassenger = Constants.passengerMultipliers[params.roadPassengerMultiplier + 1],
roadCargo = Constants.cargoMultipliers[params.roadCargoMultiplier + 1],
passengerAir = Constants.passengerMultipliers[params.passengerAirMultiplier + 1],
airCargo = Constants.cargoMultipliers[params.cargoAirMultipliers + 1],
waterMu = Constants.passengerMultipliers[params.waterMultiplier + 1],
priceDiv = Constants.priceRunningCost[params.priceRunningCost + 1]
}

if not multiplierCache then
return false
end

for _, modelName in ipairs(allVehicles) do
local modelId = api.res.modelRep.find(modelName)
local modelData = api.res.modelRep.get(modelId)

-- only touch things that actually behave like vehicles
if modelData and Utils.safeGet(modelData, "metadata", "transportVehicle") then
-- pick multiplier
local chosenMultiplier = Utils.determineMultiplier(modelData, multiplierCache, multipleUnitRep)
if not chosenMultiplier then
-- We couldn't classify with confidence. Leave capacity as-is.
chosenMultiplier = 1
end

-- scale capacities
Utils.modifyCargoEntries(modelData, chosenMultiplier)

-- scale running costs / purchase price ONLY for rail vehicles.
local priceDiv = multiplierCache.priceDiv
if priceDiv and (classifyCarrier(modelData) == "rail") then
local maintenance = Utils.safeGet(modelData, "metadata", "maintenance")
local cost = Utils.safeGet(modelData, "metadata", "cost")

if maintenance and maintenance.runningCosts then
maintenance.runningCosts = Utils.mathRound(maintenance.runningCosts * priceDiv)
end
if cost and cost.price then
cost.price = Utils.mathRound(cost.price * priceDiv)
end
end
end
end
end
}
end
最后由 AksLrs 编辑于; 10 月 28 日 下午 4:04
Ims 10 月 29 日 上午 6:42 
New version was tested thoroughly against two vanilla MUs – TGV and DUALSTOX – and the three modded EMUs here discussed. Test checks:
  1. MU capacity = 2x
  2. Cargo wagon capacity = 2x
  3. Passenger wagon capacity = 2x
✅ All tests pass. New version is good. ✅

Test details on Reddit

It occurred to me I should also test cargo and passenger wagons, as well as the rest of the vehicles. Please hold.

Trams are failing. Everything else passes. Please hold for a fix.
最后由 Ims 编辑于; 10 月 29 日 上午 7:52
Ims 10 月 29 日 上午 9:44 
v2.6 has:
CARRIER = { RAIL = 1, ROAD = 2, AIR = 3, WATER = 4 }
Correct is:
CARRIER = { ROAD = 0, RAIL = 1, TRAM = 2, AIR = 3, WATER = 4 }

So two distinct issues:
  1. TRAM is missing
  2. Wrong value for ROAD
Road vehicles were still being correctly classified thanks to a fallback check. But trams map to carrier = 2 (tram in the game, road in the mod), leading to them being classified as road vehicles, and they respond to the road passenger multiplier.

The heuristic classification of trams is justified as a backup (like the road heuristic), as there exist mods that identify themselves as "tram-trains": carrier = "RAIL" but drivingLicense = "TRAM". But for the vast majority, carrier = "TRAM" is the way to identify them.

Among my mods, 25 % of trams (7 % of all vehicles) have carrier = "RAIL", but drivingLicense = "TRAM".

Minor additional note
[SmartCapacity][unclassified_vehicle] Could not classify vehicle role; capacity multiplier left as default (1x).
If possible, include some sort of vehicle name or file reference – anything to identify the vehicle. (I have not done this.)



Changes
  • Fixed road detection (primary check broken; fallback check still worked)
  • Fixed tram detection (primary check broken; trams classified as road)
  • Added support for tram-trains (dual-mode vehicles with carrier=RAIL but drivingLicense=TRAM)

Fixed version[gist.github.com]

Verified:
  1. Trams respond to tram multiplier
  2. Other vehicle types are not affected by tram multiplier

Edit: Formatting. Please, Steam. Markdown.
最后由 Ims 编辑于; 10 月 29 日 上午 9:59
< >
正在显示第 1 - 7 条,共 7 条留言
每页显示数: 1530 50