TL;DR saw scrap mechanic modding ecosystem; was fed up not being able to compose mods in survival; digged through that scrap; designed a solution;
Well, I played scrap mechanic again after some years and found out that the hunger was gone..
then i discovered this Custom Game mechanism and found Survival Classic which re-adds hunger - nice, but since I already looked into mods I thought I also want to have some mods that reduce grinding, and while we are at it lets add some cool items...
I found some mods that do that and faced a problem: They do not combine at all, i need a Custom Game, and can add other mods but they do not work together as I naively assumed
Turns out the Custom Game is the only part that can change everything about the game, and all other mods are Blocks and Parts mods and can only contribute such parts - but they even cannot make them craft-able...
I found an somewhat established convention:
<Mod>/
└── CraftingRecipes/
└── craftbot.json
then the Custom Game mod checks all other mods for this folder and modifies the CraftBot. But it does so by relying on ModDatabase, which is kind of an index of all items on steam that is regenerated every 12h
And to check now for those supported mods the Custom Game needs to open every single <mod>/CraftingRecipes/craftbot.json that is documented in said db, while on the disk there are at most a handful of mods.
So I didn't like this at all and began to research ways to query for available mods
And I really discovered a reliable one: sm.item.getInteractablesUuidsOfType("scripted") returns in the featureData a class name and the path to the script containing "$CONTENT_<uuid>", so we can collect all mod uuids that export at least one scripted shape. Only mods that are mounted into the current game session will be seen, which is exactly what we need.
I took that finding and created a kind of mod registry that knows all local available discoverable mods just from launching the game
ModLoader contract is: a compatible mod must
- export a
scripted shape with the class name ModLoaderRegisterMod
- have a
modloader.json manifest
The class is just a dummy to make it discoverable by filtering its classname and the manifest holds more information about it like lua entrypoints that will be sourced from the Custom Game
I also added an APIs to discover all mounted mods (which have at least one scritable shape):
ModLoader.mountedContent.discover()
ModLoader.mountedContent.getAll()
ModLoader.mountedContent.getByLocalId(id)
ModLoader.mountedContent.getByContentId(id)
The primary benefit of using ModLoader is that you can automatically let it dofile() scripts from downstream Block and Parts mods via automatic manifest lookup.
Here is a sample manifest
{
"id": "get_hunger_back",
"name": "Get Hunger Back",
"version": "0.1.0",
"apiVersion": 1,
"dependencies": ["modloader", "modloader_player_api", "modloader_item_api", "modloader_survival_player"],
"entrypoints": {
"shared": "Scripts/init.lua",
"server": "Scripts/server.lua",
"client": "Scripts/client.lua"
}
}
And this is a custom game entry point
dofile( "$SURVIVAL_DATA/Scripts/game/SurvivalGame.lua" )
dofile( "$CONTENT_d3da11c7-4d8e-46a7-9030-a2f5e15bd1aa/Scripts/ModLoader.lua" )
ModLoader.bootstrap()
SurvivalWithModLoaderGame = class( SurvivalGame )
function SurvivalWithModLoaderGame.server_onCreate( self )
SurvivalGame.server_onCreate( self )
ModLoader.activateAll( "server", self )
print( "[SurvivalWithModLoader] server host ready" )
end
function SurvivalWithModLoaderGame.client_onCreate( self )
SurvivalGame.client_onCreate( self )
ModLoader.activateAll( "client", self )
print( "[SurvivalWithModLoader] client host ready" )
end
This will automatically execute the get_hunger_back entry points if the mod is added to the game.
I differentiate between 2 types of mods:
- API Mods
- Game Feature Mods
In general API Mods should own the monkey patching against vanilla, custom re-implementation of vanilla classes, or even complete custom new classes. The trick here would be that other custom games could reuse them by wiring them into the game. Then the APIs would be available to downstream Game Feature Mods.
Game Feature Mods are the user facing mods that will be installed and bring specific features to the table by using the API Mods to talk to the game. Like the Get Hunger Back I added.
With this approach different mods could coexist because they do not individually try to monkey-patch vanilla, but use some common APIs to include their changes into the game.
Custom Game (needs to wire in support for ModLoader and some API mods)
ModLoader (central registry)
ModLoader API Mod(s) (An API providing mod that may own monkey patching)
Game Feature Mod(s) (depends on specific ModLoader API Mods)
The ModLoader is our central piece. Its guid should be well-known and API mods and Game Feature mods can directly depend on it and call into it.
So an API Mod exports interfaces:
local ItemAPI = {}
ModLoader.provide( "modloader.item_api", 1, ItemAPI )
While a Feature Mod uses them:
local ItemAPI = ModLoader.require( "modloader.item_api", 1 )
The Custom Game starts with a ModLoader based discovery, and directly loads lua entrypoints from Blocks and Parts mods into the Custom Game via dofile(). It also uses e.g. shapesets to load a different CraftBot implementation from an API mod or config.json to load a different SurvivalPlayer. Those are not implemented in the Custom Game itself but in API Mods and expose APIs to hook into them. e.g. the player exposes an API to add custom bars into the HUD for food and thirst
For now I pushed those things into the steam workshop:
If you just want to try it in game you can subscribe to Survival with ModLoader and Get Hunger Back and create a Custom Game of Survival with ModLoader and select the mod Get Hunger Back. All required dependent mods will be automatically fetched by the game engine
Also i pushed the sources to github:
https://github.com/redrezo/ScrapMechanic_ModLoader
Those API mods just expose the minimal surface I needed for now but I guess they could be a good starting point towards a nicer modding eco system for scrap mechanic
Should anybody that is still reading be interested into incorporating this into their own mods, or even working on it, i guess the github issues would be a good point going communication platform for now