Help us improve
Share bugs, ideas, or general feedback.
From fs25-modding
Guides FS25 Lua mod development: writing specializations, modDesc.xml setup, overriding base functions, debugging scripts, and analyzing decompiled game scripts from dataS/scripts/.
npx claudepluginhub paint-a-farm/fs25-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/fs25-modding:fs25-scripting-modThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
FS25 mods extend the game via Lua scripts. Always reference the base game scripts to understand existing patterns before writing new code.
Debugs FS25 mods by analyzing log.txt errors, fixing common Lua nil/XML issues, troubleshooting crashes, and using in-game console commands.
Guides Unity modding architectures: asset loading via Addressables/Bundles, Lua/MoonSharp scripting, Harmony patching, mod managers, Steam Workshop, extensible designs for community mods.
Provides specialized guidance for Godot Engine projects: .gd, .tscn, .tres file formats, component-based patterns, signals, resources, debugging, validation tools, templates, and CLI workflows.
Share bugs, ideas, or general feedback.
FS25 mods extend the game via Lua scripts. Always reference the base game scripts to understand existing patterns before writing new code.
IMPORTANT: Before writing any mod script, search the base game scripts for how the game implements similar functionality. The decompiled scripts are the primary reference.
Base game scripts: dataS/scripts/ (relative to game data root)
The game ships compiled .l64 bytecode. Decompile with fs-luau-decompile from fs-utils:
cd fs-utils && cargo run --bin fs-luau-decompile -- path/to/script.l64
# Recursive:
cd fs-utils && cargo run --bin fs-luau-decompile -- -r path/to/scripts/
| Directory | Contains |
|---|---|
vehicles/specializations/ | Vehicle specializations (Motorized, Drivable, Fillable, etc.) |
placeables/specializations/ | Placeable specializations |
specialization/ | SpecializationManager, TypeManager, SpecializationUtil |
gui/ | GUI elements, dialogs, screens |
events/ | Network event classes |
economy/ | Economy, prices, selling points |
environment/ | Weather, seasons, time |
animals/ | Animal husbandry system |
field/ | Field state, ownership, missions |
fruits/ | Fruit types and growth |
internalMods/ | DLC/internal mods (e.g. FS25_precisionFarming) |
| File | Purpose |
|---|---|
main.lua | Entry point, class system (source(), Class()) |
BaseMission.lua | Mission lifecycle, loading |
FSBaseMission.lua | FS-specific mission logic |
events.lua | Event registration |
MessageCenter.lua | Pub/sub messaging system |
MessageType.lua | Message type constants |
FS25_ModName/
modDesc.xml # Mod metadata, script entry points
scripts/
MySpecialization.lua
events/
MyEvent.lua
<modDesc descVersion="106">
<!-- Specialization type -->
<specializations>
<specialization name="mySpec" className="MySpecialization" filename="scripts/MySpecialization.lua" />
</specializations>
<!-- Attach to vehicle types -->
<vehicleTypes>
<type name="myVehicle" parent="baseDrivable">
<specialization name="mySpec" />
</type>
</vehicleTypes>
<!-- Or: standalone script -->
<extraSourceFiles>
<sourceFile filename="scripts/main.lua" />
</extraSourceFiles>
</modDesc>
The standard pattern for vehicle/placeable specializations:
MySpecialization = {}
function MySpecialization.prerequisitesPresent(specializations)
return SpecializationUtil.hasSpecialization(Drivable, specializations)
end
function MySpecialization.registerEventListeners(vehicleType)
SpecializationUtil.registerEventListener(vehicleType, "onLoad", MySpecialization)
SpecializationUtil.registerEventListener(vehicleType, "onUpdate", MySpecialization)
SpecializationUtil.registerEventListener(vehicleType, "onDelete", MySpecialization)
end
function MySpecialization.registerFunctions(vehicleType)
SpecializationUtil.registerFunction(vehicleType, "myCustomFunction", MySpecialization.myCustomFunction)
end
function MySpecialization.registerOverwrittenFunctions(vehicleType)
SpecializationUtil.registerOverwrittenFunction(vehicleType, "someBaseFunction", MySpecialization.someBaseFunction)
end
function MySpecialization:onLoad(savegame)
self.spec_mySpecialization = {}
-- init
end
function MySpecialization:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
-- per-frame logic
end
Use registerOverwrittenFunction — calls your function with the original as first arg:
function MySpecialization.registerOverwrittenFunctions(vehicleType)
SpecializationUtil.registerOverwrittenFunction(vehicleType, "getCanBeSelected", MySpecialization.getCanBeSelected)
end
function MySpecialization:getCanBeSelected(superFunc)
if someCondition then
return false
end
return superFunc(self)
end
| Event | When |
|---|---|
onLoad | Vehicle/placeable loaded from XML |
onPostLoad | After all specializations loaded |
onDelete | Object being deleted |
onUpdate | Every frame (active vehicles) |
onUpdateTick | Every network tick |
onDraw | Render pass |
onReadStream / onWriteStream | Network sync (initial) |
onReadUpdateStream / onWriteUpdateStream | Network sync (updates) |
saveToXMLFile | Savegame write |
MyEvent = {}
local MyEvent_mt = Class(MyEvent, Event)
InitEventClass(MyEvent, "MyEvent")
function MyEvent.emptyNew()
return Event.new(MyEvent_mt)
end
function MyEvent.new(vehicle, value)
local self = MyEvent.emptyNew()
self.vehicle = vehicle
self.value = value
return self
end
function MyEvent:readStream(streamId, connection)
self.vehicle = NetworkUtil.readNodeObject(streamId)
self.value = streamReadFloat32(streamId)
self:run(connection)
end
function MyEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.vehicle)
streamWriteFloat32(streamId, self.value)
end
function MyEvent:run(connection)
if self.vehicle ~= nil then
self.vehicle:myFunction(self.value, true)
end
end
log.txt in the game data folder for errors~ key, use gsToggleStats for performance overlayattempt to index nil usually means a specialization dependency is missing or spec_ table not initializeddataS/scripts/ for similar functionality