Repository navigation
MiNT Multiplayer v0.6.0-dev
Pre-releaseMiNT Multiplayer v0.6.0
Version 0.6.0 introduces a launcher API for third-party applications, a new entity outline system, 3D UI improvements, expanded client/server events, resource build improvements, and client performance fixes.
Highlights
- Launcher API: Control settings, updates, server resource preparation, and game launch through a JSON-based
stdin/stdoutinterface. - Entity outlines: Highlight players, vehicles, and other entities with contour, glow, or fill effects.
- 3D UI improvements: Added vehicle rendering support and scene snapshots that return a GTA texture.
- Expanded APIs: New waypoint, attachment, model-change, weapon-damage, and damage-reporting events.
- Performance and stability: Reduced per-frame work and fixed issues affecting audio, startup, and vehicle synchronization.
Launcher
Launcher architecture
The launcher browser has been moved out of the game client core to make the client easier to extend.
Launcher API for custom applications
The launcher now supports an API mode for external applications. Communication uses JSON messages over standard input and standard output (stdin/stdout).
Start the launcher in API mode:
Launcher.exe --apiIn this mode, the launcher runs without showing its window and waits for commands.
Ready event
When initialization is complete, the launcher emits:
{"event":"ready","commands":["settings.get","settings.set","servers.list","update","prepare","launch","cancel","trust.confirm","shutdown"]}Each request contains:
| Field | Description |
|---|---|
id |
A positive integer that uniquely identifies the request. |
command |
The name of the command to execute. |
| Additional fields | Command-specific parameters. |
Available commands
| Command | Description |
|---|---|
settings.get |
Read settings from client.toml. |
settings.set |
Update the specified launcher settings. |
servers.list |
Retrieve the server list in result.servers. |
update |
Check for and install MiNT updates without starting GTA. |
prepare |
Verify and download resources for a selected server, without updating MiNT or launching the game. GTA does not need to be installed. |
launch |
Update MiNT, prepare server resources, and launch GTA. |
cancel |
Request cancellation of an active operation. |
trust.confirm |
Respond to a server-key-change confirmation request. |
shutdown |
Cancel the active operation, wait for cleanup, and stop the launcher API. |
Request examples
Each line below represents an individual JSON request.
Read settings:
{"id":1,"command":"settings.get"}Update settings:
{"id":2,"command":"settings.set","settings":{"playerName":"Player","voiceVolume":80}}Supported settings.set fields:
playerNamegameDirectoryvoiceVolumemicrophoneVolumemicrophoneEnableddiscordEnabled
Volume settings accept integer values from 0 to 100.
List servers:
{"id":3,"command":"servers.list"}Update MiNT:
{"id":4,"command":"update"}Prepare server resources:
{"id":5,"command":"prepare","server":"127.0.0.1:22005"}prepare does not update MiNT, does not require GTA to be installed, and does not start the game.
Launch GTA:
{"id":6,"command":"launch","server":"127.0.0.1:22005"}Running prepare first is optional. If suitable resources have already been prepared, launch reuses the existing cache.
Optional launch request fields:
| Field | Description |
|---|---|
gameDirectory |
Path to the GTA installation. If omitted, the launcher uses the saved path and automatic detection. If GTA is not found, an error is returned without opening a folder selection dialog. |
offline |
Skip the MiNT update step. Server resource verification still runs. |
{"id":7,"command":"launch","server":"127.0.0.1:22005","gameDirectory":"D:\\Games\\GTA V","offline":true}Cancel an operation:
{"id":8,"command":"cancel","targetId":7}Confirm a server key change:
{"id":9,"command":"trust.confirm","targetId":7,"requestId":"1","accepted":true}targetId identifies the operation awaiting confirmation. requestId is the string supplied by the trust.required event. Set accepted to true or false according to the user's choice.
Shut down the launcher API:
{"id":10,"command":"shutdown"}Responses and events
Each request receives one final response with the same id.
Successful response:
{"id":5,"ok":true,"result":{"status":"ready"}}Error response:
{"id":5,"ok":false,"error":{"code":"PREPARATION_FAILED","message":"Description of the error"}}Use error.code for programmatic error handling and error.message for a human-readable description.
Typical results:
| Operation | Result |
|---|---|
| MiNT update | status: "ready", or "skipped" when the update is skipped in offline mode. |
prepare |
status: "ready" |
launch |
status: "handedOff" after the first successful connection. |
cancel |
status: "cancelling"; the cancelled operation reports its final result separately after cleanup. |
trust.confirm |
status: "accepted", indicating the decision has been received for processing. |
shutdown |
status: "stopped", after which the process exits. |
Intermediate events include an event property and the id of the associated request.
Progress events report update, resource preparation, or launch progress:
stage:update,prepare, orlaunch.phase: the current activity within that stage.completedBytesandtotalBytes: byte progress for the current phase.totalBytes: nullmeans the total is unknown.- Launch events may provide a
messageinstead of byte counters.
{"id":5,"event":"progress","stage":"prepare","phase":"resources","completedBytes":5000,"totalBytes":10000}Server trust events: A trust.required event indicates that a server key has changed and a user decision is required. It includes requestId, server, previousKey, and candidateKey. Show the user the server and both keys, then send trust.confirm with their decision.
Concurrency: Only one operation can run at a time. While it is active, regular commands return BUSY; cancel, trust.confirm, and shutdown remain available. After a final response, the launcher API continues accepting commands. Determine an operation's result from its JSON response, not from the launcher's process exit code.
Python integration example
The following sample starts the launcher API, lists available servers, prepares a selected server, launches GTA, and shuts down the API. Update LAUNCHER to match the installation path.
import itertools
import json
import subprocess
LAUNCHER = r"D:\MiNT\Launcher.exe"
def main():
with subprocess.Popen(
[LAUNCHER, "--api"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
creationflags=subprocess.CREATE_NO_WINDOW,
) as process:
ids = itertools.count(1)
def send(command, **arguments):
request_id = next(ids)
payload = {"id": request_id, "command": command, **arguments}
process.stdin.write(
(json.dumps(payload, ensure_ascii=False) + "\n").encode("utf-8")
)
process.stdin.flush()
return request_id
def receive():
line = process.stdout.readline()
if not line:
raise RuntimeError("MiNT API closed the connection before responding")
return json.loads(line.decode("utf-8"))
def request(command, **arguments):
request_id = send(command, **arguments)
while True:
message = receive()
if "ok" in message:
if not message["ok"]:
error = message["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
if message["id"] == request_id:
return message["result"]
# Responses to trust.confirm also pass through this loop.
elif message.get("event") == "progress":
stage = message.get("stage", "")
if "message" in message:
print(stage, message["message"])
else:
done = message.get("completedBytes", 0)
total = message.get("totalBytes")
print(
stage,
message.get("phase", ""),
f"{done}/{total or '?'} bytes",
)
elif message.get("event") == "trust.required":
print("Server key has changed:", message["server"])
print("Previous key:", message["previousKey"])
print("New key:", message["candidateKey"])
accepted = (
input("Accept the new key? [yes/no]: ")
.strip()
.lower()
== "yes"
)
send(
"trust.confirm",
targetId=message["id"],
requestId=message["requestId"],
accepted=accepted,
)
try:
ready = receive()
if ready.get("event") != "ready":
raise RuntimeError(f"API is not ready: {ready}")
try:
for server in request("servers.list")["servers"]:
host = server["host"]
address = f"[{host}]" if ":" in host else host
print(f"{server['name']} — {address}:{server['port']}")
except RuntimeError as error:
print("Server list is unavailable:", error)
endpoint = input("Server address (host:port): ").strip()
request("prepare", server=endpoint)
print("Resources are ready. Launching GTA...")
result = request("launch", server=endpoint)
print("Launch result:", result["status"])
request("shutdown")
finally:
try:
process.stdin.close()
except BrokenPipeError:
pass
for _ in process.stdout:
pass
process.wait()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("Operation stopped.")
except (OSError, RuntimeError) as error:
print("Error:", error)Console Commands and Configuration
New console commands
| Command | Description |
|---|---|
mic-volume |
Adjust microphone volume. |
voice-volume |
Adjust the volume of other players' voice chat. |
netgraph |
Display FPS, ping, and frame time. |
Expanded game configuration
The [pools] section in gameConfig has been expanded from 6 to 21 properties.
Entity Outline System
Added an API for highlighting entities, with three rendering styles:
| Option | Values | Description |
|---|---|---|
style |
contour, glow, fill |
Sharp outer outline, soft outer glow, or filled silhouette. Default: contour. |
mode |
visible, xray |
Respect depth testing or render through other geometry. Default: xray. |
color |
{ r, g, b, a } |
RGBA components from 0 to 255, including alpha. Default: (0, 255, 255, 230). |
width |
1–8 |
Outline thickness or glow radius in pixels. Default: 2. Ignored for fill. |
TypeScript API
export interface IOutlineOptions {
/** Rendering style. Default: 'contour'. */
style?: 'contour' | 'glow' | 'fill';
/** Visibility mode. Default: 'xray'. */
mode?: 'visible' | 'xray';
/** RGBA values from 0 to 255. Default: (0, 255, 255, 230). */
color?: IRGBA;
/** Thickness/radius in pixels (1–8). Default: 2. Ignored for 'fill'. */
width?: number;
}
export class Outline {
constructor(options?: IOutlineOptions);
readonly valid: boolean;
readonly size: number;
/** Replaces options; omitted properties reset to defaults. */
options: IOutlineOptions;
/** Accepts an Entity or GTA script ID, not a network ID. */
add(target: Entity | number): boolean;
remove(target: Entity | number): boolean;
has(target: Entity | number): boolean;
/** Removes all targets while preserving the group and its options. */
clear(): void;
/** Destroys the group and releases its slot. */
destroy(): void;
}
/** Highlights one target. A subsequent call replaces the previous target. */
export function setOutline(
target: Entity | number,
options?: IOutlineOptions,
): boolean;
/** Clears the current resource's single-target outline. */
export function clearOutline(): void;Usage example
Press F6 to highlight the local player and their vehicle (if present), or F9 to clear the highlights.
import * as mint from 'mint-client';
const selected = new mint.Outline({
style: 'glow',
mode: 'visible',
width: 6,
color: new mint.RGBA(0, 255, 180, 220),
});
mint.on('keyup', key => {
if (key === mint.KeyCode.F6) {
selected.clear();
if (!selected.add(mint.Player.local)) {
mint.logWarning('Failed to add the player to the outline.');
}
const vehicle = mint.Player.local.vehicle;
if (vehicle && !selected.add(vehicle)) {
mint.logWarning('Failed to add the vehicle to the outline.');
}
} else if (key === mint.KeyCode.F9) {
selected.clear();
}
});Options can be updated at runtime:
selected.options = {
...selected.options,
mode: 'xray',
};3D UI Improvements
UI3DScene now supports vehicle rendering. A new snapshot method also returns a GTA texture containing the rendered scene.
Render a 3D scene
import * as mint from 'mint-client';
const scene = new mint.Utils.UI3DScene('PAUSE_SINGLE_RIGHT', {
overlay: true,
});
scene.setPed(0, mint.Player.local, {
pos: { x: 0, y: 0.1, z: 0 },
rot: { x: 0, y: 0, z: 15 },
lightIntensity: 0.8,
});
const tick = mint.everyTick(() => scene.render());
mint.on('resourceStop', name => {
if (name !== mint.resourceName) return;
mint.clearEveryTick(tick);
scene.destroy();
});Capture and display a scene snapshot
The following example captures a texture at the current screen aspect ratio, limiting the maximum dimension to 2048 pixels, then displays it using drawSprite.
import * as mint from 'mint-client';
import * as native from 'natives';
const scene = new mint.Utils.UI3DScene('PAUSE_SINGLE_RIGHT');
scene.setPed(0, mint.Player.local, {
rot: { x: 0, y: 0, z: 15 },
lightIntensity: 0.8,
});
const screen = mint.getScreenResolution();
const scale = Math.min(1, 2048 / Math.max(screen.x, screen.y));
let image;
try {
image = await scene.snapshot({
size: {
x: Math.max(1, Math.round(screen.x * scale)),
y: Math.max(1, Math.round(screen.y * scale)),
},
timeout: 5000,
});
} finally {
scene.destroy(); // The captured texture remains valid.
}
const tick = mint.everyTick(() => {
if (!image.valid) return;
native.drawSprite(
image.textureDict, image.textureName,
0.5, 0.5, 1, 1,
0, 255, 255, 255, 255,
false, false,
);
});
mint.on('resourceStop', name => {
if (name !== mint.resourceName) return;
mint.clearEveryTick(tick);
image.destroy();
});API and Event Updates
Waypoint events
Added events for creating, removing, and reaching map waypoints:
waypointCreated: (position: Vector3) => void;
waypointRemoved: (position: Vector3) => void;
waypointReached: (position: Vector3) => void;Entity attachment events
Added events for attaching and detaching entities, including vehicle-related attachment types:
entityAttached: (
parent: Entity,
child: Entity,
kind: shared.AttachmentKind
) => void;
entityDetached: (
parent: Entity,
child: Entity,
kind: shared.AttachmentKind
) => void;
export enum AttachmentKind {
GenericPhysical = 0, // Standard physical attachment
TrailerHitch = 1, // Trailer
TowArm = 2, // Tow truck
CargobobHook = 3, // Cargobob hook
CargobobMagnet = 4, // Cargobob magnet
}Added TypeScript declarations for vehicle attachment events:
vehicleAttach: (target: Vehicle, child: Vehicle) => void;
vehicleDetach: (target: Vehicle, child: Vehicle) => void;Entity model changes
Added the entityModelChange event for client/server model-change notifications:
entityModelChange: (
entity: Entity,
oldModel: number,
newModel: number
) => void;Client-side weaponDamage event
weaponDamage: (
target: Entity,
weaponHash: number,
damage: number,
offset: Vector3,
bodyPart: BodyPart,
sourceEntity: Entity
) => boolean | number | void;Updated server-side weaponDamage event
weaponDamage: (
source: Player,
target: Entity,
weaponHash: number,
damage: number,
shotOffset: shared.IVector3,
bodyPart: shared.BodyPart,
sourceEntity: Entity
) => boolean | number | void;Parameter descriptions:
| Parameter | Description |
|---|---|
source |
Player who sent the hit request to the server. |
target |
Entity receiving the hit. |
weaponHash |
Weapon hash. |
damage |
Incoming damage value; the final HP reduction is determined by GTA's damage calculations. |
offset / shotOffset |
Hit location in the target's local coordinates. |
bodyPart |
Value from BodyPart, e.g. Head = 20, Unknown = -1. |
sourceEntity |
Entity that caused the hit. |
Server API change: The previous boneId parameter has been replaced with BodyPart, and sourceEntity has been added.
Server-side damage events
Added events reporting damage that was actually applied:
playerDamage: (
victim: Player,
attacker: Entity | null,
healthDamage: number,
armourDamage: number,
weaponHash: number
) => void;
pedDamage: (
ped: Ped,
attacker: Entity | null,
healthDamage: number,
armourDamage: number,
weapon: number
) => void | boolean;
vehicleDamage: (
vehicle: Vehicle,
attacker: Entity | null,
bodyHealthDamage: number,
additionalBodyHealthDamage: number,
engineHealthDamage: number,
petrolTankDamage: number,
weaponHash: number
) => void;Call native functions by hash
Added invoke for calling GTA native functions by hash:
export function invoke(
hash: string | bigint,
...args: unknown[]
): unknown;Example:
import * as native from 'natives';
const gameTimer = native.invoke('0x9CD27B0045628463');New statSetInt native method
export function statSetInt(
statName: number,
value: number,
save: boolean
): boolean;Resource Build and Game Data
- Standalone streaming assets can now be packaged without placeholder metadata.
- Fixed an issue affecting small resource packages.
- Added and integrated
game-data.json, containing information about built-in vehicles, characters, weapons, clothing, model skeletons, color palettes, and wheel categories.
Performance, Synchronization, and Fixes
- Reduced unnecessary entity-state reads and per-frame processing to improve FPS.
- Fixed an audio-related cause of game freezes.
- Fixed the client hanging during startup.
- Fixed disconnections when spawning vehicles with invalid model hashes.
- Increased controller limits to match the number of entities within the streaming area.
- Removed the
disconnectconsole command.
Compatibility Notes
- Server-side
weaponDamage: Replace the formerboneIdhandling withBodyPartand account for the newsourceEntityparameter. - Console: The
disconnectcommand is no longer available.