CSVExporter 2 - Export your cars to CSV files!

EXTENDING CSV EXPORTER: A HOW-TO GUIDE

Good news: CSV Exporter allows you to use your own exporter script!

It’s not super well-documented though. You have a sample Lua script, JSON file and such, but that’s really it. So, let’s go through and document it a little, shall we? This post will assume some general familiarity with programming languages, general concepts and such. Let’s get into it.

PART ONE: THE SAMPLE LUA SCRIPT

Let’s start with how the sample script works, shall we? For what it’s worth, I haven’t fully worked out the boilerplate. Why not? Because Endfinity didn’t comment any of the source code and I don’t wanna wade through all of the assorted uncommented C++ source. Well, not more than I already did to find out where the files need to be. Maybe another time.

Anyway, here’s the file if you wanna refer to it rather than downloading and looking at it locally. So, the file has three functions and some code. The function we really care about is…

CExporter.ExportCarData(CarCalculator)

This single function is the one most responsible for turning Automation’s internal data structures into something that the script and such can handle. It’s also the only part of the Lua script you need to modify. So, let’s look at the beginning of the function, shall we?

    local CarInfo = CarCalculator.CarInfo
    local EngineInfo = CarInfo.TrimInfo.EngineInfo
    local GearboxInfo = CarInfo.TrimInfo.Gearbox
    local Results = CarInfo.TrimInfo.Results
    local SuspensionDetails = CarInfo.TrimInfo.SuspensionDetails

    local carParameters = CarCalculator:GetCarParameters()

    local Data = {}

So, what does this do? Well, it… Loads a bunch of information from CarCalculator and unpacks them all. But what are all of these things it refers to?

Well… If you open the game, open a car and hit F9, it becomes pretty obvious. Open the Lua tab, open the Lua Car option, then you see one labelled CarInfo (oh boy!) which has something labelled TrimInfo (oh boy!) which contains entries such as EngineInfo, Gearbox, Results (oh boy!)

Yep, it’s loading the data you see in the F9 menu into a Lua script. That makes things nice and easy for us. What about that last line? Well, braces are used to create tables in Lua - which work similarly to dicts in other languages. Scrolling down, we get a lot of lines like this:

	Data.ChassisType = CarInfo.PlatformInfo.Chassis.Name
	Data.ChassisMat = CarInfo.PlatformInfo.ChassisMaterial.Name
	Data.PanelMat = CarInfo.PlatformInfo.PanelMaterial.Name
	Data.SuspensionFront = CarCalculator.CarInfo.PlatformInfo.FrontSuspension.Name
	Data.SuspensionRear = CarCalculator.CarInfo.PlatformInfo.RearSuspension.Name

So, this populates a bunch of fields into that Data object - so Data.ChassisType is populated with whatever the F9 info says the Chassis’s name is. That might be something like Chassis_LightTruckMonocoque_Name - that’s the chassis currently known as “Partial Monocoque”, which used to be known as “Light Truck Monocoque”. It just keeps going on down the list like that, and a lot of the script is ultimately busywork like this - take a value from the internals, copy it to Data under some other name. We’ll get down to why it went where it did a little later. Okay, what about lines like this?

    --Data.BodyHardtop = CarInfo.TrimInfo.Body.ConvertableHard
    --Data.BodySofttop = CarInfo.TrimInfo.Body.ConvertableSoft

Well, lines starting with two dashes are comments in Lua, so these lines do nothing.

Let’s get to the actually interesting parts, shall we?

    --for i, v in ipairs(allEmissionCycles) do
    --    local tp = type(v)
    --    local vd = dump(v)
    --    local br = tp + vd
    --    if v.Passrate >= 100 then
    --        wesLevel = math.max(wesLevel, i)
    --    end
    --end

This is commented out (because it stopped being the good way to do this, it can be directly accessed) but what did it do before? Simple, it iterated through the possible test levels and such, checked to see if that level had passed, and stored it. There’s a slight potential error around WES9 but this is a good illustrative example of how we can do some more complex maths right here. We can put any Lua code we want in here, in fact! It’ll run, we just need to write stuff to Data.

Which is where the example right underneath it comes in.

130 lines of code
	local function CalculateCost(engineeringTime, productionUnits, engineeringCost, materialCost, toolingCosts, 
									employeeCount, employeeWage, automationCoef, shiftCount)
		local employeeCostsPerShift = employeeCount * employeeWage * 8
		local factoryProductionUnits = productionUnits / (employeeCount * automationCoef)

		local carsMadePerShift = 8 / factoryProductionUnits
		local carsMadePerDay = carsMadePerShift * shiftCount
		local employeeCostsPerDay = employeeCostsPerShift * shiftCount

		local carsMadePerMonth = carsMadePerDay * 30
		local employeeCostsPerMonth = employeeCostsPerDay * 30
		local employeeCostsPerCar = employeeCostsPerShift / carsMadePerShift

		local monthlyEngineeringCosts = engineeringCost / 60
		local engineeringCostsPerCar = monthlyEngineeringCosts / carsMadePerMonth

		local toolingCosts = toolingCosts * (shiftCount / 2)

		local totalCostPerCar = materialCost + engineeringCostsPerCar + employeeCostsPerCar + toolingCosts

		return totalCostPerCar
	end --CalculateCost

		--Data.EngineEngineeringTime
		--Data.EngineProductionUnits
		--Data.EngineEngineeringCost
		--Data.EngineMaterialCost
		--Data.EngineToolingCosts

		--Data.TrimEngineeringTime
		--Data.TrimManHours
		--Data.TrimEngCosts
		--Data.MatCost
		--Data.ToolingCosts

	--Medium factory, cheap labor, medium automation, 2 shifts
	Data.TrimCostPreset0 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											200, 10, 1.5, 2)
	Data.EngineCostPreset0 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											200, 10, 1.5, 2)
	Data.CarCostPreset0 = Data.TrimCostPreset0 + Data.EngineCostPreset0

	--Large factory, cheap labor, medium automation, 2 shifts
	Data.TrimCostPreset1 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											500, 10, 1.5, 2)
	Data.EngineCostPreset1 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											500, 10, 1.5, 2)
	Data.CarCostPreset1 = Data.TrimCostPreset1 + Data.EngineCostPreset1

	--Medium factory, average labor, high automation, 2 shifts
	Data.TrimCostPreset2 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											150, 20, 2, 2)
	Data.EngineCostPreset2 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											150, 20, 2, 2)
	Data.CarCostPreset2 = Data.TrimCostPreset2 + Data.EngineCostPreset2

	--Medium factory, average labor, high automation, 3 shifts
	Data.TrimCostPreset3 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											100, 20, 2, 3)
	Data.EngineCostPreset3 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											100, 20, 2, 3)
	Data.CarCostPreset3 = Data.TrimCostPreset3 + Data.EngineCostPreset3

	--Small factory, average labor, medium automation, 2 shifts
	Data.TrimCostPreset4 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											50, 20, 1.5, 2)
	Data.EngineCostPreset4 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											50, 20, 1.5, 2)
	Data.CarCostPreset4 = Data.TrimCostPreset4 + Data.EngineCostPreset4

	--Small factory, cheap labor, medium automation, 3 shifts
	Data.TrimCostPreset5 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											50, 10, 1.5, 3)
	Data.EngineCostPreset5 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											50, 10, 1.5, 3)
	Data.CarCostPreset5 = Data.TrimCostPreset5 + Data.EngineCostPreset5

	--Tiny factory, cheap labor, no automation, 2 shifts
	Data.TrimCostPreset6 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											10, 10, 1, 2)
	Data.EngineCostPreset6 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											10, 10, 1, 2)
	Data.CarCostPreset6 = Data.TrimCostPreset6 + Data.EngineCostPreset6

	--Tiny factory, average labor, no automation, 2 shifts
	Data.TrimCostPreset7 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											10, 20, 1, 2)
	Data.EngineCostPreset7 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											10, 20, 1, 2)
	Data.CarCostPreset7 = Data.TrimCostPreset7 + Data.EngineCostPreset7

	--Tiny factory, expensive labor, no automation, 1 shift
	Data.TrimCostPreset8 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											10, 30, 1, 1)
	Data.EngineCostPreset8 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											10, 30, 1, 1)
	Data.CarCostPreset8 = Data.TrimCostPreset8 + Data.EngineCostPreset8

	--Medium factory, very cheap labor, low automation, 3 shifts
	Data.TrimCostPreset9 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											200, 5, 1.2, 3)
	Data.EngineCostPreset9 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											200, 5, 1.2, 3)
	Data.CarCostPreset9 = Data.TrimCostPreset9 + Data.EngineCostPreset9

	--Large factory, very cheap labor, low automation, 3 shifts
	Data.TrimCostPreset10 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											500, 5, 1.2, 3)
	Data.EngineCostPreset10 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											500, 5, 1.2, 3)
	Data.CarCostPreset10 = Data.TrimCostPreset10 + Data.EngineCostPreset10

	--Large factory, average labor, medium automation, 2 shifts
	Data.TrimCostPreset11 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											500, 20, 1.5, 2)
	Data.EngineCostPreset11 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											500, 20, 1.5, 2)
	Data.CarCostPreset11 = Data.TrimCostPreset11 + Data.EngineCostPreset11

	--Small factory, expensive labor, high automation, 2 shifts
	Data.TrimCostPreset12 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											50, 30, 2, 2)
	Data.EngineCostPreset12 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											50, 30, 2, 2)
	Data.CarCostPreset12 = Data.TrimCostPreset12 + Data.EngineCostPreset12

	--Tiny factory, expensive labor, high automation, 2 shifts
	Data.TrimCostPreset13 = CalculateCost(Data.TrimEngineeringTime, Data.TrimManHours, Data.TrimEngCosts, Data.MatCost, Data.ToolingCosts,
											10, 30, 2, 2)
	Data.EngineCostPreset13 = CalculateCost(Data.EngineEngineeringTime, Data.EngineProductionUnits, Data.EngineEngineeringCost, Data.EngineMaterialCost, Data.EngineToolingCosts,
											10, 30, 2, 2)
	Data.CarCostPreset13 = Data.TrimCostPreset13 + Data.EngineCostPreset13

	return Data

So, what does this long example does? Well, it defines a function inside the ExportCarData function, then uses that a bunch of times. We also get the one example of comments here, examples of what different presets mean. This is really, really useful for us, for two reasons. One, it shows us how we can define a function and reuse it. Two, it shows us how the JSON is useful.

PART TWO: THE JSON FILE

So, we have a Data object filled with many, many fields… What happens with it? It goes back up through the boilerplate and into the innards of the exporter DLL and I don’t need to care about it, because I can look at the JSON and work it all out.

Looking at the “Dataset” JSON file, early on, we get something familiar:

		{"ModelName": {"Translation": "Model Name", "DataType": "DataType_String"}},
		{"ModelYear": {"Translation": "Model Year", "DataType": "DataType_Float"}},
		{"BodyWheelbase": {"Translation": "Wheelbase", "DataType": "DataType_Float"}},
		{"ChassisType": {"Translation": "Chassis Type", "DataType": "DataType_String"}},
		{"ChassisMat": {"Translation": "Chassis Material", "DataType": "DataType_String"}},

Just a few lines in, and we already start to get the names that were used before - ChassisType, ChassisMat, stuff like that. There’s some that were added on elsewhere, but this is nice and useful, this is just what we want. In fact, the items listed in the JSON and not commented out are all of the fields we get in an export result - and extra fields, like CarCostPreset13? They’re in the Data object from the Lua script, but they’re not in the JSON, so they don’t go into the export.

There’s a second JSON, the Translations file. Whenever a string would be output, it’s first checked in the Translations file. If the string has a field in the JSON, it’s replaced. For instance, there’s a line in the translations file that says

		"Piston_LowFCast_Name": "Low-Friction Cast",

So this turns an internal name into a readable one. Unfortunately, it’s followed by…

		"R": "Rear",

… which turns the tyre index R to Rear

image

PART THREE: PUTTING IT ALL TOGETHER

So, what do we do to create our own custom exporter script? Just follow these simple steps:

  • Make a new copy of the JSON and Lua files somewhere.
  • Use the F9 menu to find out where the information you want is stored. Want to find out how much a car’s offroad is being changed due to braking at low load? That’s stored under Results.OffroadFactors.factorBrakeReserves
  • Add some code to the new copy of the Lua script to add a new field into the Data object, something like Data.OffroadFactorBrakeReserves = Results.OffroadFactors.factorBrakeReserves. This can be simple or complex.
  • Add a line to the Dataset JSON which includes the name you used internally, the name you want to use externally and the appropriate data type, something like {“OffroadFactorBrakeReserves”: {“Translation”: “Offroad Brake Reserve Factor”, “DataType”: “DataType_Float”}}
  • Place your new files in %LocalAppData%\AutomationGame\CSVExporter 2\UserFiles
  • Set the “Lua File” parameter to the file name you picked for the Lua file. Do the same for the Dataset file, and so on.

Exports.zip (5.4 KB)

Here’s an example - placing the Lua file and the JSON dataset into the UserFiles folder and running CSVExporter with these files will generate something like the CSV file included in that .zip.

3 Likes