This guide is for making .tuff mods for TuffClient and how to enable and manage installed mods.
A .tuff file is simply a JSON file made of two things:
meta- mod info. ex: username, minimum client version, etc.files- JS code (JSON serialized and one line), what the mod does
It is not a Java .jar mod and is not like Fabric or Forge in any way; there is no compiled java code.
- Open the Modules menu by pressing
Right Shift(or from the escape menu or main menu) - Scroll to the bottom and press
Add Modsat the bottom - Pick one or more
.tufffiles - Press
Manage Modsto enable, disable, uninstall, etc.
If a mod makes drastic changes, it can use api.reloadClient() to restart the game.
You can make a lot with this system, not only UI:
- HUD widgets (text, timers, status)
- PvP trainers (like WTap timing help)
- QoL tools (chat macros, key tools, view tools)
- Camera/perspective tools (freelook, third person)
- Cosmetic changes and client side changes
- Settings saved per mod
- Tick-based and key-based actions
Mods should stay legit:
- HUD
- trainers
- cosmetics
- QoL These are safe examples
Do not make cheat mods: aim assist, velocity, auto clickers, unfair reach, macros that give a PVP advantage, etc.
{
"meta": {
"id": "my-mod-id",
"name": "My Mod",
"version": "1.0.0",
"author": "You",
"entry": "main.js",
"minClientVersion": "1.0.0",
"permissions": ["hud", "events", "modules"]
},
"files": {
"main.js": "module.exports = function(api){ api.log('loaded'); };"
}
}meta.idmust start with a letter or number, and only use letters, numbers, or - and _meta.entrymust exist infiles(thats the js file name. ex."main.js": "module.exports = function(api){ api.log('loaded'); };"here in that example it's main.js)permissionsmust be an strings. They tell the user what the mod does. What it changes.minClientVersionmust not be newer than the client. If it is, it will fail to open.- Entry must export a function:
module.exports = function(api){ ... }(you need to use the runtime API, or else my JS api won't work)
tick->{ now, delta }render2d->{ ctx, width, height, partialTicks }keyDown->{ keyCode, isRepeat }keyUp->{ keyCode }attackEntity->{ targetId, isPlayer, targetName?, reach? }
Key codes can be found on the Minecraft Wiki
api.versionapi.on(event, fn)/api.off(event, fn)api.registerHUD(id, renderFn)/api.unregisterHUD(id)api.registerModule(def)api.getSetting(key, fallback)api.setSetting(key, value)api.drawText(ctx, text, x, y, opts?)api.toast(message, type?)api.log(...args)
These can change gameplay too:
api.setPerspective(mode)where mode is0,1,2api.getPerspective()api.setFreelookEnabled(bool)api.isFreelookEnabled()api.resetFreelook()api.sendChat(message)api.setSprint(bool)api.setSneak(bool)api.setHudHidden(bool)api.setGamma(number)api.setFov(number)api.reloadClient()
Possible permissions:
"permissions": ["hud", "events", "modules", "unsafe"]If you add "unsafe" in permissions, your mod gets full power mode:
- allows browser globals in runtime
- allows
evalandFunction(these allow you to do more complex things) - allows
api.getUnsafeGlobal()
Use this careful, because unsafe mods can change almost everything.
- If install fails, read the stacktrace in the text popup.
- Keep
main.jssmall at first and then if it works make it bigger - Check JSON commas, etc.
- Check
meta.entryfile name exactly matches. - If a mod acts weird, reload once and test again.
- Make custom text and status on screen with
api.registerHUD(...) - Update visuals every frame with the
render2devent - React every tick with the
tickevent - Change camera/view settings:
api.setPerspective(mode)(0first person,1third person back,2third person front)api.setFreelookEnabled(bool)api.resetFreelook()
- Control entity culling for non-player entities (Players are rendered normally):
api.setMobCullingEnabled(bool)api.setMobCullingDistance(number)
Rendering mods are mods that change what the player views on their screen, without changing server.
module.exports = function(api){
api.registerHUD("my-hud", function(ctx){
if(!ctx) return;
api.drawText(ctx, "My Render Mod Active", 10, 20, {
color: "#ffffff",
font: "16px monospace"
});
});
};{
"meta": {
"id": "hello-hud",
"name": "Hello HUD",
"version": "1.0.0",
"author": "Community",
"entry": "main.js",
"minClientVersion": "1.0.0",
"permissions": ["hud", "events", "modules"]
},
"files": {
"main.js": "module.exports = function(api){ api.registerHUD('hello', function(ctx){ if(!ctx) return; api.drawText(ctx, 'Tuff Mod Loaded', 10, 20, { color: '#ffffff', font: '16px monospace' }); }); };"
}
}{
"meta": {
"id": "freelook-toggle",
"name": "Freelook Toggle",
"version": "1.0.0",
"author": "Community",
"entry": "main.js",
"minClientVersion": "1.0.0",
"permissions": ["hud", "events", "modules"]
},
"files": {
"main.js": "module.exports = function(api){ var key=46; var on=false; api.on('keyDown', function(e){ if(e.keyCode===key && !e.isRepeat){ on=!on; api.setFreelookEnabled(on); if(!on){ api.resetFreelook(); } } }); api.registerHUD('free', function(ctx){ if(!ctx||!on) return; api.drawText(ctx, 'Freelook ON', 10, 40, { color:'#aaffaa', font:'16px monospace' }); }); };"
}
}{
"meta": {
"id": "quick-gg",
"name": "Quick GG",
"version": "1.0.0",
"author": "Community",
"entry": "main.js",
"minClientVersion": "1.0.0",
"permissions": ["hud", "events", "modules"]
},
"files": {
"main.js": "module.exports = function(api){ var key=34; api.on('keyDown', function(e){ if(e.keyCode===key && !e.isRepeat){ api.sendChat('gg'); api.toast('Sent: gg'); } }); };"
}
}