diff --git a/docs/en/API documentation/Client-Side.md b/docs/en/API documentation/Client-Side.md new file mode 100644 index 0000000..97e31dd --- /dev/null +++ b/docs/en/API documentation/Client-Side.md @@ -0,0 +1,1297 @@ +## Table of Contents + +### MPVehicleGE +- [Vehicle Functions](#vehicle-functions) +- [Player Functions](#player-functions) +- [Nametag Functions](#nametag-functions) +- [Role Functions](#role-functions) +- [Navigation Functions](#navigation-functions) +- [Object Methods](#object-methods) +- [Event Hooks](#event-hooks) + +### MPConfig +- [MPConfig Functions](#mpconfig-functions) + +### MPCoreNetwork +- [MPCoreNetwork Functions](#mpcorenetwork-functions) + +### MPGameNetwork +- [Event System Functions](#event-system-functions) +- [Keypress Functions](#keypress-functions) +- [UI Functions](#ui-functions) +- [MPGameNetwork Callbacks](#mpgamenetwork-callbacks) + +### MPHelpers +- [Encoding Functions](#encoding-functions) +- [Color Functions](#color-functions) +- [String Functions](#string-functions) +- [Table Functions](#table-functions) +- [Debug Functions](#debug-functions) + +--- + +## Vehicle Functions + +### `getGameVehicleID(serverVehicleID)` +Resolves a serverVehicleID into the gameVehicleID + +**Parameters:** +- `serverVehicleID` (string) - Format: "X-Y" where X is PlayerID and Y is VehicleID + +**Returns:** +- (number) - The game's internal vehicle ID +- (number) `-1` - If vehicle is unknown + +**Usage:** +```lua +local gameID = extensions.MPVehicleGE.getGameVehicleID("0-0") +``` + +--- + +### `getServerVehicleID(gameVehicleID)` +Resolves a gameVehicleID into the serverVehicleID + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Returns:** +- (string) - Server vehicle ID (e.g., "0-0") +- (nil) - If gameVehicleID is unknown + +**Usage:** +```lua +local serverID = extensions.MPVehicleGE.getServerVehicleID(11171) +``` + +--- + +### `getVehicleByServerID(serverVehicleID)` +Returns the complete vehicle table for this vehicle + +**Parameters:** +- `serverVehicleID` (string) - Format: "X-Y" + +**Returns:** +- (table) - Vehicle information (name, gameVehicleID, jbeam, ownerID, ownerName, isLocal, isSpawned, etc.) +- (nil) - If serverVehicleID is invalid + +**Usage:** +```lua +local vehicle = extensions.MPVehicleGE.getVehicleByServerID("0-0") +if vehicle then + print("Owner: " .. vehicle.ownerName) +end +``` + +--- + +### `getVehicleByGameID(gameVehicleID)` +Returns the complete vehicle table for this vehicle + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Returns:** +- (table) - Vehicle information +- (nil) - If gameVehicleID is invalid + +**Usage:** +```lua +local vehicle = extensions.MPVehicleGE.getVehicleByGameID(11171) +``` + +--- + +### `isOwn(gameVehicleID)` +Checks if the given vehicle belongs to this client + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Returns:** +- (boolean) - True if vehicle belongs to this client + +**Usage:** +```lua +if extensions.MPVehicleGE.isOwn(11171) then + print("This is my vehicle") +end +``` + +--- + +### `getOwnMap()` +Returns a table containing all vehicles owned by this client + +**Parameters:** +- None + +**Returns:** +- (table) - Map of owned vehicles `{[gameVehicleID] = vehicles_subtable}` + +**Usage:** +```lua +local myVehicles = extensions.MPVehicleGE.getOwnMap() +``` + +--- + +### `getVehicleMap()` +Returns a table of all known multiplayer vehicles + +**Parameters:** +- None + +**Returns:** +- (table) - Map of all vehicles `{[serverVehicleID] = gameVehicleID}` + +**Usage:** +```lua +local allVehicles = extensions.MPVehicleGE.getVehicleMap() +``` + +--- + +### `getDistanceMap()` +Returns the distance from each multiplayer vehicle to this client's point of view + +**Parameters:** +- None + +**Returns:** +- (table) - Map of distances `{[gameVehicleID] = distanceInMeters}` + +**Usage:** +```lua +local distances = extensions.MPVehicleGE.getDistanceMap() +``` + +--- + +### `getNicknameMap()` +Returns all multiplayer gameVehicleIDs with their owner names + +**Parameters:** +- None + +**Returns:** +- (table) - Map of nicknames `{[gameVehicleID] = ownerName}` + +**Usage:** +```lua +local nicknameMap = extensions.MPVehicleGE.getNicknameMap() +``` + +--- + +### `getVehicles()` +Returns the complete vehicles table + +**Parameters:** +- None + +**Returns:** +- (table) - All vehicles `{[serverVehicleID] = vehicles_subtable}` + +**Usage:** +```lua +local vehicles = extensions.MPVehicleGE.getVehicles() +for serverID, vehicle in pairs(vehicles) do + print("Vehicle: " .. vehicle.jbeam) +end +``` + +--- + +## Player Functions + +### `getPlayerByName(name)` +Returns this player's table and ID + +**Parameters:** +- `name` (string) - The player's name + +**Returns:** +- (table) - Player information (name, playerID, role, vehicles, etc.) +- (number) - The player's ID +- (nil) - If player not found + +**Usage:** +```lua +local player, playerID = extensions.MPVehicleGE.getPlayerByName("John") +if player then + print("Player ID: " .. playerID) +end +``` + +--- + +### `getPlayers()` +Returns the complete players table + +**Parameters:** +- None + +**Returns:** +- (table) - All players `{[playerID] = players_subtable}` + +**Usage:** +```lua +local players = extensions.MPVehicleGE.getPlayers() +for playerID, player in pairs(players) do + print("Player: " .. player.name) +end +``` + +--- + +## Nametag Functions + +### `setPlayerNickPrefix(targetName, tagSource, text)` +Adds a prefix to a player's nametag (displayed before the name) + +**Parameters:** +- `targetName` (string) - The player's name +- `tagSource` (string) - Unique identifier for this prefix +- `text` (string) - Text to display before the name + +**Usage:** +```lua +extensions.MPVehicleGE.setPlayerNickPrefix("John", "RANK", "1st.") +-- Result: "1st. John" +``` + +--- + +### `setPlayerNickSuffix(targetName, tagSource, text)` +Adds a suffix to a player's nametag (displayed after the name) + +**Parameters:** +- `targetName` (string) - The player's name +- `tagSource` (string) - Unique identifier for this suffix +- `text` (string) - Text to display after the name + +**Usage:** +```lua +extensions.MPVehicleGE.setPlayerNickSuffix("John", "STATUS", "[AFK]") +-- Result: "John [AFK]" +``` + +--- + +### `hideNicknames(hide)` +Turns on or off the nametag drawing from BeamMP + +**Parameters:** +- `hide` (boolean) - True to hide nametags, false to show them + +**Usage:** +```lua +extensions.MPVehicleGE.hideNicknames(true) -- Hide +extensions.MPVehicleGE.hideNicknames(false) -- Show +``` + +--- + +### `toggleNicknames()` +Toggles the displaying of nametags + +**Parameters:** +- None + +**Usage:** +```lua +extensions.MPVehicleGE.toggleNicknames() +``` + +--- + +## Role Functions + +### `setPlayerRole(playerID, tag, shorttag, red, green, blue)` +Sets a custom role for a player + +**Parameters:** +- `playerID` (number) - ID of the player +- `tag` (string) - Role tag (e.g., "VIP") +- `shorttag` (string) - Short version (e.g., "V") +- `red` (number) - Red channel (0-255) +- `green` (number) - Green channel (0-255) +- `blue` (number) - Blue channel (0-255) + +**Returns:** +- (boolean, string) - `false, "player not found"` if player doesn't exist +- (boolean, string) - `false, error` if invalid arguments +- (nil) - Nothing on success + +**Usage:** +```lua +local success, error = extensions.MPVehicleGE.setPlayerRole(0, "VIP", "V", 255, 215, 0) +if success == false then + print("Error: " .. error) +end +``` + +--- + +### `clearPlayerRole(playerID)` +Clears a custom role for a player + +**Parameters:** +- `playerID` (number) - ID of the player + +**Returns:** +- (boolean) - Always returns `false` (implementation quirk - use to check if player exists) + +**Usage:** +```lua +extensions.MPVehicleGE.clearPlayerRole(0) +``` + +--- + +### `setVehicleRole(playerIDVehicleID, tag, shorttag, red, green, blue)` +Sets a custom role for a specific vehicle + +**Parameters:** +- `playerIDVehicleID` (string) - Vehicle ID (format: "0-0") +- `tag` (string) - Role tag +- `shorttag` (string) - Short version +- `red` (number) - Red (0-255) +- `green` (number) - Green (0-255) +- `blue` (number) - Blue (0-255) + +**Returns:** +- (boolean, string) - `false, "vehicle not found"` if vehicle doesn't exist +- (boolean, string) - `false, error` if invalid arguments +- (nil) - Nothing on success + +**Usage:** +```lua +local success, error = extensions.MPVehicleGE.setVehicleRole("0-0", "Police", "POL", 0, 0, 255) +if success == false then + print("Error: " .. error) +end +``` + +--- + +### `clearVehicleRole(playerIDVehicleID)` +Clears a custom role for a vehicle + +**Parameters:** +- `playerIDVehicleID` (string) - Vehicle ID (format: "0-0") + +**Returns:** +- (boolean) - Always returns `false` (implementation quirk - use to check if vehicle exists) + +**Usage:** +```lua +extensions.MPVehicleGE.clearVehicleRole("0-0") +``` + +--- + +## Navigation Functions + +### `groundmarkerToPlayer(targetName)` +Sets a ground marker route to target player's position (static) + +**Parameters:** +- `targetName` (string) - Player's name, or nil to clear + +**Usage:** +```lua +extensions.MPVehicleGE.groundmarkerToPlayer("John") -- Set +extensions.MPVehicleGE.groundmarkerToPlayer(nil) -- Clear +``` + +--- + +### `groundmarkerFollowPlayer(targetName, dontfollow)` +Sets a ground marker route that follows target player + +**Parameters:** +- `targetName` (string) - Player's name, or nil to stop +- `dontfollow` (boolean) - If true, creates static marker + +**Usage:** +```lua +extensions.MPVehicleGE.groundmarkerFollowPlayer("John") -- Follow +extensions.MPVehicleGE.groundmarkerFollowPlayer("John", true) -- Static +extensions.MPVehicleGE.groundmarkerFollowPlayer(nil) -- Stop +``` + +--- + +### `queryRoadNodeToPosition(targetPosition, owner)` +Finds the closest road nodes to a target position + +**Parameters:** +- `targetPosition` (vec3 or table) - Target position with x, y, z +- `owner` (string) - Optional identifier (default: "target") + +**Returns:** +- (boolean) - Success status +- (number) - nodeID (if successful) + +**Usage:** +```lua +local pos = vec3(100, 200, 50) +local success, nodeID = extensions.MPVehicleGE.queryRoadNodeToPosition(pos) +``` + +--- + +## Object Methods + +### Player Object Methods + +#### `player:setNickPrefix(tagSource, text)` +Sets a prefix for this player's nametag + +**Parameters:** +- `tagSource` (string) - Unique identifier +- `text` (string) - Text to display (or nil to remove) + +**Usage:** +```lua +local player = extensions.MPVehicleGE.getPlayerByName("John") +if player then + player:setNickPrefix("STATUS", "[AFK]") +end +``` + +--- + +#### `player:setNickSuffix(tagSource, text)` +Sets a suffix for this player's nametag + +**Parameters:** +- `tagSource` (string) - Unique identifier +- `text` (string) - Text to display (or nil to remove) + +**Usage:** +```lua +local player = extensions.MPVehicleGE.getPlayerByName("John") +if player then + player:setNickSuffix("MISSION", "[In Mission]") +end +``` + +--- + +#### `player:setCustomRole(role)` +Sets a custom role for this player + +**Parameters:** +- `role` (table) - Role table: `{backcolor = {r, g, b}, tag = string, shorttag = string}` + +**Usage:** +```lua +local player = extensions.MPVehicleGE.getPlayerByName("John") +if player then + player:setCustomRole({ + backcolor = {r = 255, g = 0, b = 0}, + tag = " [VIP]", + shorttag = " [V]" + }) +end +``` + +--- + +#### `player:clearCustomRole()` +Clears the custom role for this player + +**Usage:** +```lua +local player = extensions.MPVehicleGE.getPlayerByName("John") +if player then + player:clearCustomRole() +end +``` + +--- + +### Vehicle Object Methods + +#### `vehicle:getOwner()` +Returns the owner of this vehicle + +**Returns:** +- (table) - Player object +- (number) - Player's ID + +**Usage:** +```lua +local vehicle = extensions.MPVehicleGE.getVehicleByServerID("0-0") +if vehicle then + local owner, ownerID = vehicle:getOwner() + print("Owner: " .. owner.name) +end +``` + +--- + +#### `vehicle:setCustomRole(role)` +Sets a custom role for this vehicle + +**Parameters:** +- `role` (table) - Role table: `{backcolor = {r, g, b}, tag = string, shorttag = string}` + +**Usage:** +```lua +local vehicle = extensions.MPVehicleGE.getVehicleByServerID("0-0") +if vehicle then + vehicle:setCustomRole({ + backcolor = {r = 0, g = 0, b = 255}, + tag = " [Police]", + shorttag = " [POL]" + }) +end +``` + +--- + +#### `vehicle:clearCustomRole()` +Clears the custom role for this vehicle + +**Usage:** +```lua +local vehicle = extensions.MPVehicleGE.getVehicleByServerID("0-0") +if vehicle then + vehicle:clearCustomRole() +end +``` + +--- + +#### `vehicle:setDisplayName(displayName)` +Sets a custom display name for this vehicle + +**Parameters:** +- `displayName` (string) - Custom name to display + +**Usage:** +```lua +local vehicle = extensions.MPVehicleGE.getVehicleByServerID("0-0") +if vehicle then + vehicle:setDisplayName("Patrol Car #1") +end +``` + +--- + +## Event Hooks + +BeamMP provides event hooks that you can override to execute custom code when specific events occur. **Do not call these functions directly** - instead, override them while preserving the original functionality. + +### Hook Pattern + +Always preserve the original function when overriding: + +```lua +-- Save the original function +local originalCallback = MPVehicleGE.onVehicleSpawned + +-- Override with your custom logic +MPVehicleGE.onVehicleSpawned = function(gameVehicleID) + -- Call the original first + originalCallback(gameVehicleID) + + -- Your custom code here + print("Vehicle spawned: " .. gameVehicleID) +end +``` + +--- + +### Available Event Hooks + +#### `onUpdate(dt)` +Called every frame while connected to multiplayer + +**Parameters:** +- `dt` (number) - Delta time in seconds since last frame + +**Usage:** +```lua +local originalOnUpdate = MPVehicleGE.onUpdate +MPVehicleGE.onUpdate = function(dt) + originalOnUpdate(dt) + -- Your frame-by-frame logic here +end +``` + +--- + +#### `onPreRender(dt)` +Called every frame before rendering + +**Parameters:** +- `dt` (number) - Delta time in seconds + +**Note:** +This handles nametag rendering, distance calculations, and ground markers internally. + +**Usage:** +```lua +local originalOnPreRender = MPVehicleGE.onPreRender +MPVehicleGE.onPreRender = function(dt) + originalOnPreRender(dt) + -- Your pre-render logic here +end +``` + +--- + +#### `onVehicleSpawned(gameVehicleID)` +Called when a vehicle spawns (both local and remote) + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Usage:** +```lua +local originalOnVehicleSpawned = MPVehicleGE.onVehicleSpawned +MPVehicleGE.onVehicleSpawned = function(gameVehicleID) + originalOnVehicleSpawned(gameVehicleID) + + local vehicle = extensions.MPVehicleGE.getVehicleByGameID(gameVehicleID) + if vehicle then + print(vehicle.ownerName .. " spawned a " .. vehicle.jbeam) + end +end +``` + +--- + +#### `onVehicleDestroyed(gameVehicleID)` +Called when a vehicle is destroyed/removed + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Usage:** +```lua +local originalOnVehicleDestroyed = MPVehicleGE.onVehicleDestroyed +MPVehicleGE.onVehicleDestroyed = function(gameVehicleID) + local vehicle = extensions.MPVehicleGE.getVehicleByGameID(gameVehicleID) + if vehicle then + print("Vehicle " .. vehicle.jbeam .. " was destroyed") + end + + originalOnVehicleDestroyed(gameVehicleID) +end +``` + +--- + +#### `onVehicleSwitched(oldGameVehicleID, newGameVehicleID)` +Called when player switches between vehicles + +**Parameters:** +- `oldGameVehicleID` (number) - Previous vehicle ID (or -1) +- `newGameVehicleID` (number) - New vehicle ID (or -1) + +**Usage:** +```lua +local originalOnVehicleSwitched = MPVehicleGE.onVehicleSwitched +MPVehicleGE.onVehicleSwitched = function(oldID, newID) + originalOnVehicleSwitched(oldID, newID) + + print("Switched from vehicle " .. oldID .. " to " .. newID) +end +``` + +--- + +#### `onVehicleResetted(gameVehicleID)` +Called when a vehicle is reset (local vehicles only) + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Usage:** +```lua +local originalOnVehicleResetted = MPVehicleGE.onVehicleResetted +MPVehicleGE.onVehicleResetted = function(gameVehicleID) + originalOnVehicleResetted(gameVehicleID) + + print("Vehicle " .. gameVehicleID .. " was reset") +end +``` + +--- + +#### `onVehicleColorChanged(gameVehicleID, index, paint)` +Called when a vehicle's paint color is changed + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID +- `index` (number) - Paint slot index (0, 1, or 2) +- `paint` (table) - Paint data with color information + +**Usage:** +```lua +local originalOnVehicleColorChanged = MPVehicleGE.onVehicleColorChanged +MPVehicleGE.onVehicleColorChanged = function(gameVehicleID, index, paint) + originalOnVehicleColorChanged(gameVehicleID, index, paint) + + print("Vehicle " .. gameVehicleID .. " changed paint slot " .. index) +end +``` + +--- + +#### `onVehicleReady(gameVehicleID)` +Called when a vehicle's extensions have loaded and the vehicle is fully ready + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Note:** +Use this instead of `onVehicleSpawned` if you need vehicle extensions to be loaded. + +**Usage:** +```lua +local originalOnVehicleReady = MPVehicleGE.onVehicleReady +MPVehicleGE.onVehicleReady = function(gameVehicleID) + originalOnVehicleReady(gameVehicleID) + + -- Safe to interact with vehicle extensions here + local veh = be:getObjectByID(gameVehicleID) + if veh then + veh:queueLuaCommand("print('Vehicle is ready!')") + end +end +``` + +--- + +#### `onUIInitialised()` +Called when the BeamMP UI is initialized + +**Usage:** +```lua +local originalOnUIInitialised = MPVehicleGE.onUIInitialised +MPVehicleGE.onUIInitialised = function() + originalOnUIInitialised() + + print("BeamMP UI initialized") +end +``` + +--- + +#### `onSettingsChanged()` +Called when BeamMP settings are changed + +**Usage:** +```lua +local originalOnSettingsChanged = MPVehicleGE.onSettingsChanged +MPVehicleGE.onSettingsChanged = function() + originalOnSettingsChanged() + + print("BeamMP settings changed") +end +``` + +--- + +## MPConfig Functions + +### `MPConfig.getPlayerServerID()` +Returns the local player's server-assigned ID + +**Returns:** +- (number) - The player's server ID (-1 if not set) + +**Usage:** +```lua +local myID = extensions.MPConfig.getPlayerServerID() +``` + +--- + +### `MPConfig.getNickname()` +Returns the local player's nickname + +**Returns:** +- (string) - The player's current nickname + +**Usage:** +```lua +local name = extensions.MPConfig.getNickname() +``` + +--- + +### `MPConfig.getConfig()` +Returns the BeamMP configuration settings + +**Returns:** +- (table) - Configuration table with all BeamMP settings +- (nil) - If config file doesn't exist + +**Usage:** +```lua +local config = extensions.MPConfig.getConfig() +``` + +--- + +### `MPConfig.setConfig(settingName, settingVal)` +Sets a specific configuration value + +**Parameters:** +- `settingName` (string) - Name of the setting +- `settingVal` (any) - Value to set + +**Usage:** +```lua +extensions.MPConfig.setConfig("myCustomSetting", true) +``` + +--- + +## MPCoreNetwork Functions + +### `MPCoreNetwork.getCurrentServer()` +Returns information about the current connected server + +**Returns:** +- (table) - Server data (ip, port, name, map) +- (nil) - If not connected + +**Usage:** +```lua +local server = extensions.MPCoreNetwork.getCurrentServer() +if server then + print("Server: " .. server.name) + print("IP: " .. server.ip .. ":" .. server.port) +end +``` + +--- + +## Event System Functions + +### `TriggerServerEvent(name, data)` +Sends an event to the server + +**Parameters:** +- `name` (string) - Event name +- `data` (string) - Data to send + +**Note:** +Global function. The server must have a registered handler for this event. + +**Usage:** +```lua +TriggerServerEvent("playerReady", "ready") + +-- With JSON +local data = {position = {x=100, y=200, z=50}} +TriggerServerEvent("updatePlayer", jsonEncode(data)) +``` + +--- + +### `TriggerClientEvent(name, data)` +Triggers a local client event + +**Parameters:** +- `name` (string) - Event name +- `data` (string) - Data to send + +**Note:** +Global function. Triggers locally without sending to server. + +**Usage:** +```lua +TriggerClientEvent("localUpdate", "data") +``` + +--- + +### `AddEventHandler(event_name, func, name)` +Registers a function to handle a specific event + +**Parameters:** +- `event_name` (string) - Name of the event to handle +- `func` (function) - Handler function (receives event data) +- `name` (string) - Optional internal name + +**Note:** +Global function. + +**Usage:** +```lua +AddEventHandler("playerDamage", function(data) + print("Damage: " .. data) +end) + +-- With JSON +AddEventHandler("vehicleSpawned", function(data) + local vehData = jsonDecode(data) + print("Spawned: " .. vehData.model) +end) +``` + +--- + +### `RemoveEventHandler(event_name, name)` +Removes an event handler + +**Parameters:** +- `event_name` (string) - Name of the event +- `name` (string) - Optional internal name + +**Note:** +Global function. + +**Usage:** +```lua +RemoveEventHandler("playerDamage") +``` + +--- + +## Keypress Functions + +### `onKeyPressed(keyname, func)` +Registers a function to be called when a key is pressed + +**Parameters:** +- `keyname` (string) - Name of the key (e.g., "NUMPAD1", "F1") +- `func` (function) - Function to call (receives boolean) + +**Note:** +Global function. + +**Usage:** +```lua +onKeyPressed("NUMPAD1", function(state) + print("NUMPAD1 pressed!") +end) +``` + +--- + +### `onKeyReleased(keyname, func)` +Registers a function to be called when a key is released + +**Parameters:** +- `keyname` (string) - Name of the key +- `func` (function) - Function to call (receives boolean) + +**Note:** +Global function. + +**Usage:** +```lua +onKeyReleased("NUMPAD1", function(state) + print("NUMPAD1 released!") +end) +``` + +--- + +### `addKeyEventListener(keyname, func, type)` +Registers a key event listener with customizable trigger type + +**Parameters:** +- `keyname` (string) - Name of the key +- `func` (function) - Function to call +- `type` (string) - Event type: "down", "up", or "both" (default: "both") + +**Note:** +Global function. + +**Usage:** +```lua +addKeyEventListener("F1", function(isPressed) + if isPressed then + print("F1 pressed") + else + print("F1 released") + end +end, "both") +``` + +--- + +### `getKeyState(keyname)` +Returns the current state of a key + +**Parameters:** +- `keyname` (string) - Name of the key + +**Returns:** +- (boolean) - True if pressed, false otherwise + +**Note:** +Global function. Only works for keys registered with addKeyEventListener. + +**Usage:** +```lua +local isPressed = getKeyState("NUMPAD1") +if isPressed then + print("NUMPAD1 is held down") +end +``` + +--- + +## UI Functions + +### `MPGameNetwork.spawnUiDialog(dialogInfo)` +Creates a custom interactive dialog box + +**Parameters:** +- `dialogInfo` (table) - Dialog configuration: + - `title` (string) - Dialog title (optional) + - `body` (string) - Dialog message (optional) + - `buttons` (table) - Button configurations (optional) + - `class` (string) - "experimental" for hazard lines (optional) + - `interactionID` (string) - Interaction identifier (optional) + - `reportToServer` (boolean) - Send to server (optional, default: false) + - `reportToExtensions` (boolean) - Trigger local event (optional, default: false) + +**Usage:** +```lua +-- Simple dialog +extensions.MPGameNetwork.spawnUiDialog({ + title = "Welcome", + body = "Welcome to the server!" +}) + +-- Choice dialog +extensions.MPGameNetwork.spawnUiDialog({ + title = "Choose Team", + body = "Which team?", + buttons = { + {label = "Red", key = "joinRed"}, + {label = "Blue", key = "joinBlue"} + }, + interactionID = "teamSelection", + reportToServer = true +}) +``` + +--- + +## MPGameNetwork Callbacks + +### `MPGameNetwork.onUpdate(dt)` +Called every frame while connected to multiplayer + +**Parameters:** +- `dt` (number) - Delta time in seconds + +**Usage:** +```lua +local originalOnUpdate = MPGameNetwork.onUpdate +MPGameNetwork.onUpdate = function(dt) + originalOnUpdate(dt) + -- Your code here +end +``` + +--- + +### `MPGameNetwork.onVehicleReady(gameVehicleID)` +Called when a vehicle is ready and extensions are loaded + +**Parameters:** +- `gameVehicleID` (number) - The game's internal vehicle ID + +**Usage:** +```lua +local originalOnVehicleReady = MPGameNetwork.onVehicleReady +MPGameNetwork.onVehicleReady = function(gameVehicleID) + originalOnVehicleReady(gameVehicleID) + -- Your code here +end +``` + +--- + +## Encoding Functions + +### `MPHelpers.b64encode(string)` +Encodes a string to Base64 (RFC 2045) + +**Parameters:** +- `string` (string) - String to encode + +**Returns:** +- (string) - Base64-encoded string + +**Usage:** +```lua +local encoded = extensions.MPHelpers.b64encode("Hello World") + +-- Encoding JSON +local data = {name = "Player", score = 100} +local encoded = extensions.MPHelpers.b64encode(jsonEncode(data)) +TriggerServerEvent("sendData", encoded) +``` + +--- + +### `MPHelpers.b64decode(string)` +Decodes a Base64 string (RFC 2045) + +**Parameters:** +- `string` (string) - Base64-encoded string + +**Returns:** +- (string) - Decoded string + +**Usage:** +```lua +local decoded = extensions.MPHelpers.b64decode("SGVsbG8gV29ybGQ=") + +-- Decoding JSON +AddEventHandler("receiveData", function(data) + local decoded = extensions.MPHelpers.b64decode(data) + local jsonData = jsonDecode(decoded) +end) +``` + +--- + +## Color Functions + +### `MPHelpers.hex2rgb(hex)` +Converts a hexadecimal color code to RGB values + +**Parameters:** +- `hex` (string) - Hex color code (e.g., "#FF5733" or "#F57") + +**Returns:** +- (table) - RGB values `{r, g, b}` in 0-1 range +- (table) - `{0, 0, 0}` if invalid + +**Note:** +Supports both 3-character and 6-character hex codes. + +**Usage:** +```lua +local rgb = extensions.MPHelpers.hex2rgb("#FF5733") +print(rgb[1], rgb[2], rgb[3]) -- 1.0, 0.341, 0.2 + +-- Short format +local rgb = extensions.MPHelpers.hex2rgb("#F57") +``` + +--- + +## String Functions + +### `MPHelpers.splitStringToTable(string, delimiter, convert_into)` +Splits a string by delimiter and optionally converts values + +**Parameters:** +- `string` (string) - String to split +- `delimiter` (string) - Delimiter to split by +- `convert_into` (number) - Conversion type (optional): + - `nil` or `0` - Keep as strings (default) + - `1` - Convert to numbers + - `2` - Convert to booleans + +**Returns:** +- (table) - Array of split values + +**Usage:** +```lua +-- Strings +local parts = extensions.MPHelpers.splitStringToTable("Hello,World", ",") +-- {"Hello", "World"} + +-- Numbers +local nums = extensions.MPHelpers.splitStringToTable("10,20,30", ",", 1) +-- {10, 20, 30} + +-- Parse coordinates +local coords = extensions.MPHelpers.splitStringToTable("100,200,50", ",", 1) +local x, y, z = coords[1], coords[2], coords[3] +``` + +--- + +## Table Functions + +### `MPHelpers.tableDiff(old, new)` +Compares two tables and returns their differences + +**Parameters:** +- `old` (table) - First table to compare +- `new` (table) - Second table to compare + +**Returns:** +- (table) `diff` - All keys that differ +- (table) `o` - Values from old that differ +- (table) `n` - Values from new that differ + +**Usage:** +```lua +local oldConfig = {speed = 100, damage = 50, armor = 30} +local newConfig = {speed = 120, damage = 50, armor = 40} + +local diff, oldVals, newVals = extensions.MPHelpers.tableDiff(oldConfig, newConfig) +-- diff = {speed = 120, armor = 40} + +for key, value in pairs(diff) do + print(key .. " changed from " .. oldVals[key] .. " to " .. newVals[key]) +end +``` + +--- + +## Debug Functions + +### `MPHelpers.simpletraces(level)` +Returns formatted caller information as string + +**Parameters:** +- `level` (number) - Stack level (optional, default: 2) + +**Returns:** +- (string) - Formatted string: `"source:line, namewhat name"` +- (string) - `"unknown"` if info not available + +**Usage:** +```lua +local function myFunction() + local caller = extensions.MPHelpers.simpletraces() + print("Called from: " .. caller) +end +``` + +--- + +### `MPHelpers.simpletrace(level)` +Logs caller information to console + +**Parameters:** +- `level` (number) - Stack level (optional, default: 1) + +**Note:** +Logs the calling location to the console. + +**Usage:** +```lua +local function myFunction() + extensions.MPHelpers.simpletrace() + -- Logs: "Code was called from: lua/ge/extensions/mymod.lua:42" +end +``` + +--- + +*Last updated: 01.01.2026* diff --git a/docs/en/API documentation/Server-Side.md b/docs/en/API documentation/Server-Side.md new file mode 100644 index 0000000..6d08c2c --- /dev/null +++ b/docs/en/API documentation/Server-Side.md @@ -0,0 +1,1407 @@ +## Table of Contents + +### Global +- [Global Functions](#global-functions) + +### MP +- [Players](#mp--players) +- [Vehicles](#mp--vehicles) +- [Communication](#mp--communication) +- [Events](#mp--events) +- [Utilities](#mp--utilities) + +### Util +- [Logging](#util--logging) +- [JSON](#util--json) +- [Random](#util--random) +- [Profiling](#util--profiling) + +### Http +- [Http Functions](#http) + +### FS +- [Checks](#fs--checks) +- [Operations](#fs--operations) +- [Paths](#fs--paths) + +### Events +- [Events Reference](#events) + +--- + +## Global Functions + +### `print(...)` + +Prints to the server console, prefixed with date, time and `[LUA]`. + +**Parameters:** +- `...` (any) - Values of any type. Tables are printed with their contents. + +**Usage:** +```lua +local name = "John Doe" +print("Hello, I'm", name, "and I'm", 32) +``` + +--- + +### `printRaw(...)` + +Prints to the server console without any prefix. + +**Parameters:** +- `...` (any) - Values of any type. + +--- + +### `exit()` + +Shuts down the server gracefully. Triggers the `onShutdown` event. + +--- + +## MP — Players + +### `MP.GetPlayerCount() -> number` + +Returns the number of currently connected players. + +**Returns:** +- (number) - Player count. + +--- + +### `MP.GetPlayers() -> table` + +Returns a table of all connected players. + +**Returns:** +- (table) - Map of `{[playerID] = playerName}`. + +--- + +### `MP.GetPlayerName(playerID) -> string` + +Returns the name of a player by ID. + +**Parameters:** +- `playerID` (number) - The player's ID. + +**Returns:** +- (string) - The player's name, or `""` if not found. + +**Usage:** +```lua +local player_id = 4 +print(MP.GetPlayerName(player_id)) +``` + +--- + +### `MP.GetPlayerIDByName(name) -> number` + +Returns the ID of a player by name. + +**Parameters:** +- `name` (string) - The player's name. + +**Returns:** +- (number) - The player's ID, or `-1` if not found. + +--- + +### `MP.GetPlayerIdentifiers(playerID) -> table` + +Returns identifiers for a player such as IP, BeamMP forum ID and Discord ID. + +**Parameters:** +- `playerID` (number) - The player's ID. + +**Returns:** +- (table) - Table with keys `ip`, `beammp`, `discord` (only if linked). +- (nil) - If the player was not found. + +**Usage:** +```lua +local player_id = 5 +print(MP.GetPlayerIdentifiers(player_id)) +-- { ip: "127.0.0.1", discord: "12345678987654321", beammp: "1234567" } +``` + +--- + +### `MP.GetPlayerRole(playerID) -> string|nil` + +Returns the player's role as set by the BeamMP backend. + +**Parameters:** +- `playerID` (number) - The player's ID. + +**Returns:** +- (string) - The player's role. +- (nil) - If the player was not found. + +--- + +### `MP.IsPlayerConnected(playerID) -> boolean` + +Returns whether a UDP packet has been received from the player, i.e. whether the connection is fully established. + +**Parameters:** +- `playerID` (number) - The player's ID. + +**Returns:** +- (boolean) - `true` if fully connected. + +**Usage:** +```lua +local player_id = 8 +print(MP.IsPlayerConnected(player_id)) +``` + +--- + +### `MP.IsPlayerGuest(playerID) -> boolean` + +Returns whether the player is a guest (not registered on the BeamMP forum). + +**Parameters:** +- `playerID` (number) - The player's ID. + +**Returns:** +- (boolean) - `true` if guest. + +--- + +### `MP.DropPlayer(playerID, reason?)` + +Kicks a player from the server. + +**Parameters:** +- `playerID` (number) - The player's ID. +- `reason` (string, optional) - Reason for the kick. + +**Usage:** +```lua +function ChatHandler(player_id, player_name, message) + if string.match(message, "darn") then + MP.DropPlayer(player_id, "Profanity is not allowed") + return 1 + end +end +``` + +--- + +## MP — Vehicles + +### `MP.GetPlayerVehicles(playerID) -> table` + +Returns all vehicles of a player. + +**Parameters:** +- `playerID` (number) - The player's ID. + +**Returns:** +- (table) - Map of `{[vehicleID] = dataString}` where dataString is a raw JSON string. +- (nil) - If the player has no vehicles or was not found. + +**Usage:** +```lua +local player_id = 3 +local player_vehicles = MP.GetPlayerVehicles(player_id) + +for vehicle_id, vehicle_data in pairs(player_vehicles) do + local start = string.find(vehicle_data, "{") + local formattedVehicleData = string.sub(vehicle_data, start, -1) + print(Util.JsonDecode(formattedVehicleData)) +end +``` + +--- + +### `MP.GetPositionRaw(playerID, vehicleID) -> table, string` + +Returns the current raw position of a vehicle. + +**Parameters:** +- `playerID` (number) - The player's ID. +- `vehicleID` (number) - The vehicle's ID. + +**Returns:** +- (table) - Table with keys: `pos`, `rot`, `vel`, `rvel`, `tim`, `ping`. +- (string) - Error message if one occurred, empty string on success. + +**Note:** +Each value in `pos`, `rot`, `vel`, `rvel` is a table with indices `1, 2, 3` (and `4` for `rot`). + +**Usage:** +```lua +local player_id = 4 +local vehicle_id = 0 + +local raw_pos, error = MP.GetPositionRaw(player_id, vehicle_id) +if error == "" then + local x, y, z = table.unpack(raw_pos["pos"]) + print("X:", x, "Y:", y, "Z:", z) +else + print(error) +end +``` + +--- + +### `MP.RemoveVehicle(playerID, vehicleID)` + +Removes a vehicle belonging to a player. + +**Parameters:** +- `playerID` (number) - The player's ID. +- `vehicleID` (number) - The vehicle's ID. + +**Usage:** +```lua +local player_id = 3 +local player_vehicles = MP.GetPlayerVehicles(player_id) + +for vehicle_id, vehicle_data in pairs(player_vehicles) do + MP.RemoveVehicle(player_id, vehicle_id) +end +``` + +--- + +## MP — Communication + +### `MP.SendChatMessage(playerID, message, logChat?)` + +Sends a chat message to a specific player or everyone. + +**Parameters:** +- `playerID` (number) - The player's ID, or `-1` for everyone. +- `message` (string) - The message content. +- `logChat` (boolean, optional) - Whether to log to the server log (default: `true`). + +**Note:** +This function does not return a value. + +**Usage:** +```lua +-- To a specific player +function ChatHandler(player_id, player_name, msg) + if string.match(msg, "darn") then + MP.SendChatMessage(player_id, "Please do not use profanity.") + return 1 + end +end + +-- To everyone +MP.SendChatMessage(-1, "Hello World!") +``` + +--- + +### `MP.SendNotification(playerID, message, icon?, category?)` + +Sends a notification (popup) to a specific player or everyone. + +**Parameters:** +- `playerID` (number) - The player's ID, or `-1` for everyone. +- `message` (string) - The notification content. +- `icon` (string, optional) - Notification icon. +- `category` (string, optional) - Notification category. + +**Note:** +This function does not return a value. When called with only 3 arguments (no category), the category is automatically set to the value of message. + +--- + +### `MP.ConfirmationDialog(playerID, title, body, buttons, interactionID, warning?, reportToServer?, reportToExtensions?)` + +Sends a confirmation dialog with buttons to a player. + +**Parameters:** +- `playerID` (number) - The player's ID, or `-1` for everyone. +- `title` (string) - Dialog title. +- `body` (string) - Dialog body text. +- `buttons` (table) - Array of buttons. +- `interactionID` (string) - Unique identifier for this interaction. +- `warning` (boolean, optional) - Show warning styling (default: `false`). +- `reportToServer` (boolean, optional) - Send response to server (default: `true`). +- `reportToExtensions` (boolean, optional) - Trigger local event (default: `true`). + +**Note:** +When called with only 5 arguments the function does not return a value. When called with 6–8 arguments it returns `boolean, string`. + +--- + +### `MP.TriggerClientEvent(playerID, eventName, data) -> boolean, string` + +Sends an event to a specific client or everyone. + +**Parameters:** +- `playerID` (number) - The player's ID, or `-1` for everyone. +- `eventName` (string) - The event name. +- `data` (string) - The data to send. + +**Returns:** +- (boolean) - `true` if sent successfully. Always `true` for `-1`. +- (string) - Error message if failed. + +--- + +### `MP.TriggerClientEventJson(playerID, eventName, data) -> boolean, string` + +Same as `TriggerClientEvent` but accepts a Lua table and automatically encodes it to JSON. + +**Parameters:** +- `playerID` (number) - The player's ID, or `-1` for everyone. +- `eventName` (string) - The event name. +- `data` (table) - Lua table to be JSON-encoded and sent. + +**Returns:** +- (boolean) - `true` on success. +- (string) - Error message if failed. + +--- + +## MP — Events + +### `MP.RegisterEvent(eventName, functionName)` + +Registers a function as a handler for an event. + +**Parameters:** +- `eventName` (string) - The event name. +- `functionName` (string) - The name of the Lua function to register. + +**Note:** +If the event does not exist it is created. Multiple handlers can be registered for the same event. + +**Usage:** +```lua +function ChatHandler(player_id, player_name, msg) + if msg == "hello" then + print("Hello World!") + return 0 + end +end + +MP.RegisterEvent("onChatMessage", "ChatHandler") +``` + +--- + +### `MP.TriggerLocalEvent(eventName, ...) -> table` + +Triggers an event in the current state only. Synchronous. + +**Parameters:** +- `eventName` (string) - The event name. +- `...` (any, optional) - Arguments passed to handlers. + +**Returns:** +- (table) - Table of return values from all handlers. + +**Usage:** +```lua +local Results = MP.TriggerLocalEvent("MyEvent") +print(Results) +``` + +--- + +### `MP.TriggerGlobalEvent(eventName, ...) -> table` + +Triggers an event in all states. Asynchronous. Local handlers run synchronously and immediately. + +**Parameters:** +- `eventName` (string) - The event name. +- `...` (any, optional) - Arguments. Supported types: string, number, boolean, table. + +**Returns:** +- (table) - Future-like object with: + - `:IsDone() -> boolean` — Whether all handlers have finished. + - `:GetResults() -> table` — Return values from all handlers. + +**Note:** +Call these methods with `:` not `.`. + +**Usage:** +```lua +local Future = MP.TriggerGlobalEvent("MyEvent") +while not Future:IsDone() do + MP.Sleep(100) +end +local Results = Future:GetResults() +print(Results) +``` + +--- + +### `MP.CreateEventTimer(eventName, intervalMS, strategy?)` + +Creates a timer that repeatedly triggers an event. + +**Parameters:** +- `eventName` (string) - The event to trigger. +- `intervalMS` (number) - Interval between triggers in milliseconds. +- `strategy` (number, optional) - `MP.CallStrategy.BestEffort` (default) or `MP.CallStrategy.Precise`. + +**Note:** +Intervals below 25ms are not recommended and will not be served reliably. + +**Usage:** +```lua +local seconds = 0 + +function CountSeconds() + seconds = seconds + 1 +end + +MP.RegisterEvent("EverySecond", "CountSeconds") +MP.CreateEventTimer("EverySecond", 1000) +``` + +--- + +### `MP.CancelEventTimer(eventName)` + +Cancels an existing event timer. + +**Parameters:** +- `eventName` (string) - The event name. + +**Note:** +The event may fire one more time before being cancelled due to asynchronous behaviour. + +--- + +## MP — Utilities + +### `MP.CreateTimer() -> table` + +Creates a timer object for measuring elapsed time. + +**Returns:** +- (table) - Object with: + - `:GetCurrent() -> float` — Seconds elapsed since last Start. + - `:Start()` — Resets the timer. + +**Usage:** +```lua +local mytimer = MP.CreateTimer() +-- do stuff here that needs to be timed +print(mytimer:GetCurrent()) +``` + +--- + +### `MP.GetOSName() -> string` + +Returns the name of the server's operating system. + +**Returns:** +- (string) - `"Windows"`, `"Linux"`, or `"Other"`. + +--- + +### `MP.GetServerVersion() -> number, number, number` + +Returns the server version. + +**Returns:** +- (number) - major +- (number) - minor +- (number) - patch + +**Usage:** +```lua +local major, minor, patch = MP.GetServerVersion() +print(major, minor, patch) +``` + +--- + +### `MP.Get(configID) -> value` + +Reads a server config setting by ID. + +**Parameters:** +- `configID` (number) - ID from `MP.Settings`. + +**Returns:** +- (value) - The setting's current value. + +--- + +### `MP.Set(configID, value)` + +Temporarily changes a server config setting. The change is not saved to the config file. + +**Parameters:** +- `configID` (number) - ID from `MP.Settings`. +- `value` (any) - New value. Type must match the setting. + +**Usage:** +```lua +MP.Set(MP.Settings.Debug, true) +``` + +--- + +### `MP.Settings` + +Enum of setting IDs for use with `MP.Get` and `MP.Set`. + +```lua +MP.Settings.Debug -- 0 (boolean) +MP.Settings.Private -- 1 (boolean) +MP.Settings.MaxCars -- 2 (number) +MP.Settings.MaxPlayers -- 3 (number) +MP.Settings.Map -- 4 (string) +MP.Settings.Name -- 5 (string) +MP.Settings.Description -- 6 (string) +MP.Settings.InformationPacket -- 7 (boolean) +``` + +--- + +### `MP.CallStrategy` + +Enum for use with `MP.CreateEventTimer`. + +```lua +MP.CallStrategy.BestEffort -- Skip trigger if previous handler hasn't finished (default) +MP.CallStrategy.Precise -- Always trigger, even if it causes the queue to build up +``` + +--- + +### `MP.Sleep(ms)` + +Halts the entire current Lua state for a number of milliseconds. + +**Parameters:** +- `ms` (number) - Time to sleep in milliseconds. + +**Note:** +Nothing will execute in the state while sleeping. **Do not sleep for more than 500ms** if event handlers are registered — a sleeping state can slow the entire server down significantly. + +**Usage:** +```lua +local Future = MP.TriggerGlobalEvent("MyEvent") +while not Future:IsDone() do + MP.Sleep(100) +end +``` + +--- + +### `MP.GetStateMemoryUsage() -> number` + +Returns the memory usage of the current Lua state. + +**Returns:** +- (number) - Memory in bytes. + +--- + +### `MP.GetLuaMemoryUsage() -> number` + +Returns the total memory usage of all Lua states combined. + +**Returns:** +- (number) - Memory in bytes. + +--- + +## Util — Logging + +### `Util.LogInfo(...)`, `Util.LogWarn(...)`, `Util.LogError(...)`, `Util.LogDebug(...)` + +Prints to the server log at the corresponding level. + +**Parameters:** +- `...` (any) - Values of any type. + +**Note:** +`Util.LogDebug` is only shown when `MP.Settings.Debug` is enabled. + +**Usage:** +```lua +Util.LogInfo("Hello, World!") +Util.LogWarn("Cool warning") +Util.LogError("Oh no!") +Util.LogDebug("hi") +``` + +--- + +## Util — JSON + +### `Util.JsonEncode(table) -> string` + +Encodes a Lua table into a JSON string. + +**Parameters:** +- `table` (table) - The table to encode. + +**Returns:** +- (string) - Minified JSON string. + +**Note:** +Automatically detects array vs object based on key types. Functions, userdata and unsupported types are ignored. + +**Usage:** +```lua +local player = { + name = "Lion", + age = 69, + skills = { "skill A", "skill B" } +} +local json = Util.JsonEncode(player) +-- '{"name":"Lion","age":69,"skills":["skill A","skill B"]}' +``` + +--- + +### `Util.JsonDecode(json) -> table` + +Decodes a JSON string into a Lua table. + +**Parameters:** +- `json` (string) - A valid JSON string. + +**Returns:** +- (table) - The decoded table. +- (nil) - If the JSON is invalid. + +**Usage:** +```lua +local json = "{\"message\":\"OK\",\"code\":200}" +local tbl = Util.JsonDecode(json) +-- { message = "OK", code = 200 } +``` + +--- + +### `Util.JsonPrettify(json) -> string` + +Adds indentation and newlines to a JSON string for human readability (indent of 4). + +**Parameters:** +- `json` (string) - A valid JSON string. + +**Returns:** +- (string) - Pretty-printed JSON. + +**Usage:** +```lua +local myjson = Util.JsonEncode({ name="Lion", age = 69, skills = { "skill A", "skill B" } }) +print(Util.JsonPrettify(myjson)) +``` + +--- + +### `Util.JsonMinify(json) -> string` + +Removes unnecessary whitespace and newlines from a JSON string. + +**Parameters:** +- `json` (string) - A valid JSON string. + +**Returns:** +- (string) - Minified JSON. + +**Usage:** +```lua +local pretty = Util.JsonPrettify(Util.JsonEncode({ name="Lion", age = 69 })) +print(Util.JsonMinify(pretty)) +``` + +--- + +### `Util.JsonFlatten(json) -> string` + +Flattens a nested JSON into `/a/b/c`-style keys per RFC 6901. + +**Parameters:** +- `json` (string) - A valid JSON string. + +**Returns:** +- (string) - Flattened JSON. + +**Usage:** +```lua +local json = Util.JsonEncode({ name="Lion", skills = { "skill A", "skill B" } }) +print(Util.JsonFlatten(json)) +-- '{"/name":"Lion","/skills/0":"skill A","/skills/1":"skill B"}' +``` + +--- + +### `Util.JsonUnflatten(json) -> string` + +Restores a flattened JSON back to its nested structure. + +**Parameters:** +- `json` (string) - A flattened JSON string. + +**Returns:** +- (string) - Nested JSON. + +--- + +### `Util.JsonDiff(a, b) -> string` + +Computes a diff between two JSON strings per RFC 6902. + +**Parameters:** +- `a` (string) - First JSON string. +- `b` (string) - Second JSON string. + +**Returns:** +- (string) - JSON Patch representing the differences. + +--- + +## Util — Random + +### `Util.Random() -> float` + +Returns a random float between 0 and 1. + +**Returns:** +- (float) + +**Usage:** +```lua +local rand = Util.Random() +print("rand: " .. rand) +-- rand: 0.135477 +``` + +--- + +### `Util.RandomRange(min, max) -> float` + +Returns a random float within a given range. + +**Parameters:** +- `min` (number) - Lower bound. +- `max` (number) - Upper bound. + +**Returns:** +- (float) + +**Usage:** +```lua +local randFloat = Util.RandomRange(1, 1000) +print("randFloat: " .. randFloat) +-- randFloat: 420.6969 +``` + +--- + +### `Util.RandomIntRange(min, max) -> number` + +Returns a random integer within a given range. + +**Parameters:** +- `min` (number) - Lower bound. +- `max` (number) - Upper bound. + +**Returns:** +- (number) - Integer. + +**Usage:** +```lua +local randInt = Util.RandomIntRange(1, 100) +print("randInt: " .. randInt) +-- randInt: 69 +``` + +--- + +## Util — Profiling + +### `Util.DebugStartProfile(name)` + +Starts a named execution time measurement. + +**Parameters:** +- `name` (string) - Identifier for this measurement. + +--- + +### `Util.DebugStopProfile(name)` + +Stops a named measurement. Must be called after `DebugStartProfile` with the same name. + +**Parameters:** +- `name` (string) - Identifier for this measurement. + +--- + +### `Util.DebugExecutionTime() -> table` + +Returns execution time statistics for every handler that has run. + +**Returns:** +- (table) - Per handler: `mean`, `stdev`, `min`, `max`, `n` (all in ms). + +**Usage:** +```lua +function printDebugExecutionTime() + local stats = Util.DebugExecutionTime() + local pretty = "DebugExecutionTime:\n" + local longest = 0 + for name, t in pairs(stats) do + if #name > longest then + longest = #name + end + end + for name, t in pairs(stats) do + pretty = pretty .. string.format("%" .. longest + 1 .. "s: %12f +/- %12f (min: %12f, max: %12f) (called %d time(s))\n", name, t.mean, t.stdev, t.min, t.max, t.n) + end + print(pretty) +end +``` + +--- + +## Http + +### `Http.CreateConnection(host, port) -> table` + +Creates an HTTP connection to an external server. + +**Parameters:** +- `host` (string) - Server address. +- `port` (number) - Port number. + +**Returns:** +- (table) - Connection object with method `:Get(path, headers)`. + +--- + +### `connection:Get(path, headers)` + +Sends an HTTP GET request. + +**Parameters:** +- `path` (string) - The request path. +- `headers` (table) - Headers as `{[string] = string}`. + +--- + +## FS — Checks + +### `FS.Exists(path) -> boolean` + +Returns whether a path exists. + +**Parameters:** +- `path` (string) - The path to check. + +**Returns:** +- (boolean) - `true` if exists. + +--- + +### `FS.IsDirectory(path) -> boolean` + +Returns whether a path is a directory. + +**Parameters:** +- `path` (string) - The path to check. + +**Returns:** +- (boolean) - `true` if directory. + +**Note:** +`false` does not imply the path is a file. Use `FS.IsFile` to check separately. + +--- + +### `FS.IsFile(path) -> boolean` + +Returns whether a path is a regular file. + +**Parameters:** +- `path` (string) - The path to check. + +**Returns:** +- (boolean) - `true` if regular file. + +**Note:** +`false` does not imply the path is a directory. + +--- + +## FS — Operations + +### `FS.CreateDirectory(path) -> boolean, string` + +Creates a directory including any missing parent directories (like `mkdir -p`). + +**Parameters:** +- `path` (string) - Path of the directory to create. + +**Returns:** +- (boolean) - `true` on success. +- (string) - Error message on failure, or `""` on success. + +**Usage:** +```lua +local success, error_message = FS.CreateDirectory("data/mystuff/somefolder") + +if not success then + print("failed to create directory: " .. error_message) +end +``` + +--- + +### `FS.Remove(path) -> boolean, string` + +Removes a file or empty directory. + +**Parameters:** +- `path` (string) - The path to remove. + +**Returns:** +- (boolean) - `true` on success. +- (string) - Error message on failure. + +--- + +### `FS.Rename(path, newPath) -> boolean, string` + +Renames or moves a file or directory. + +**Parameters:** +- `path` (string) - Current path. +- `newPath` (string) - New path. + +**Returns:** +- (boolean) - `true` on success. +- (string) - Error message on failure. + +--- + +### `FS.Copy(path, newPath) -> boolean, string` + +Copies a file or directory (recursive). + +**Parameters:** +- `path` (string) - Source path. +- `newPath` (string) - Destination path. + +**Returns:** +- (boolean) - `true` on success. +- (string) - Error message on failure. + +--- + +### `FS.ListFiles(path) -> table` + +Returns a list of file names in a directory (not recursive). + +**Parameters:** +- `path` (string) - Directory path. + +**Returns:** +- (table) - Array of file names. +- (nil) - If the path does not exist. + +**Usage:** +```lua +print(FS.ListFiles("Resources/Server/examplePlugin")) +-- { 1: "example.json", 2: "example.lua" } +``` + +--- + +### `FS.ListDirectories(path) -> table` + +Returns a list of directory names inside a directory (not recursive). + +**Parameters:** +- `path` (string) - Directory path. + +**Returns:** +- (table) - Array of directory names. +- (nil) - If the path does not exist. + +**Usage:** +```lua +print(FS.ListDirectories("Resources")) +-- { 1: "Client", 2: "Server" } +``` + +--- + +## FS — Paths + +### `FS.GetFilename(path) -> string` + +Returns the filename with extension from a path. + +**Parameters:** +- `path` (string) - A path string. + +**Returns:** +- (string) - The filename. + +**Usage:** +```lua +"my/path/a.txt" -> "a.txt" +"somefile.txt" -> "somefile.txt" +"/awesome/path" -> "path" +``` + +--- + +### `FS.GetExtension(path) -> string` + +Returns the file extension including the dot. + +**Parameters:** +- `path` (string) - A path string. + +**Returns:** +- (string) - The extension (e.g. `".json"`), or `""` if none. + +**Usage:** +```lua +"myfile.txt" -> ".txt" +"somefile." -> "." +"/awesome/path" -> "" +"/awesome/path/file.zip.txt" -> ".txt" +``` + +--- + +### `FS.GetParentFolder(path) -> string` + +Returns the path of the containing directory. + +**Parameters:** +- `path` (string) - A path string. + +**Returns:** +- (string) - Parent folder path. + +**Usage:** +```lua +"/var/tmp/example.txt" -> "/var/tmp" +"/" -> "/" +"mydir/a/b/c.txt" -> "mydir/a/b" +``` + +--- + +### `FS.ConcatPaths(...) -> string` + +Joins path segments together using the system's preferred separator, resolving `..` where present. + +**Parameters:** +- `...` (string) - Path segments. + +**Returns:** +- (string) - Joined path. + +**Usage:** +```lua +FS.ConcatPaths("a", "b", "/c/d/e/", "/f/", "g", "h.txt") +-- "a/b/c/d/e/f/g/h.txt" +``` + +--- + +## Events + +### Player connection order + +``` +onPlayerAuth → onPlayerConnecting → onPlayerJoining → onPlayerJoin +``` + +--- + +### `onInit` + +Triggered right after all plugin files have been loaded. + +**Arguments:** none +**Cancellable:** no + +--- + +### `onConsoleInput` + +Triggered when the server console receives input. + +**Arguments:** +- `input` (string) - The text that was entered. + +**Cancellable:** no + +**Usage:** +```lua +function handleConsoleInput(cmd) + local delim = cmd:find(' ') + if delim then + local message = cmd:sub(delim+1) + if cmd:sub(1, delim-1) == "print" then + return message + end + end +end + +MP.RegisterEvent("onConsoleInput", "handleConsoleInput") +``` + +--- + +### `onShutdown` + +Triggered when the server shuts down, after all players have been kicked. + +**Arguments:** none +**Cancellable:** no + +--- + +### `onPlayerAuth` + +Triggered when a player attempts to connect, before any other connection events. + +**Arguments:** +- `name` (string) - Player name. +- `role` (string) - Player role from the backend. +- `isGuest` (boolean) - Whether the player is a guest. +- `identifiers` (table) - Identifiers: `ip`, `beammp`, `discord`. + +**Cancellable:** yes +- Return `1` — deny with a generic message. +- Return `string` — deny with the string as the reason. +- Return `2` — allow entry even if the server is full. + +**Usage:** +```lua +function myPlayerAuthorizer(name, role, is_guest, identifiers) + return "Sorry, you cannot join at this time." +end + +MP.RegisterEvent("onPlayerAuth", "myPlayerAuthorizer") +``` + +--- + +### `postPlayerAuth` + +Triggered after `onPlayerAuth`, regardless of whether the player was accepted or rejected. + +**Arguments:** +- `wasRejected` (boolean) - Whether the player was rejected. +- `reason` (string) - Rejection reason if rejected. +- `name` (string) - Player name. +- `role` (string) - Player role. +- `isGuest` (boolean) - Whether guest. +- `identifiers` (table) - Identifiers. + +**Cancellable:** no + +--- + +### `onPlayerConnecting` + +Triggered when a player starts connecting, after `onPlayerAuth`. + +**Arguments:** +- `playerID` (number) + +**Cancellable:** no + +--- + +### `onPlayerJoining` + +Triggered after the player has finished downloading all mods. + +**Arguments:** +- `playerID` (number) + +**Cancellable:** no + +--- + +### `onPlayerJoin` + +Triggered after the player has finished syncing and entered the game. + +**Arguments:** +- `playerID` (number) + +**Cancellable:** no + +--- + +### `onPlayerDisconnect` + +Triggered when a player disconnects. + +**Arguments:** +- `playerID` (number) + +**Cancellable:** no + +--- + +### `onChatMessage` + +Triggered when a player sends a chat message. + +**Arguments:** +- `playerID` (number) +- `playerName` (string) +- `message` (string) + +**Cancellable:** yes — returning `1` prevents the message from being shown to anyone. + +**Usage:** +```lua +function MyChatMessageHandler(sender_id, sender_name, message) + if message == "darn" then + return 1 + else + return 0 + end +end + +MP.RegisterEvent("onChatMessage", "MyChatMessageHandler") +``` + +--- + +### `postChatMessage` + +Triggered after `onChatMessage`. + +**Arguments:** +- `wasSent` (boolean) - Whether the message was sent. +- `playerID` (number) +- `playerName` (string) +- `message` (string) + +**Cancellable:** no + +--- + +### `onVehicleSpawn` + +Triggered when a player spawns a new vehicle. + +**Arguments:** +- `playerID` (number) +- `vehicleID` (number) +- `data` (string) - JSON string with vehicle configuration and positional data. + +**Cancellable:** yes — returning a value other than `0` prevents the spawn. + +--- + +### `postVehicleSpawn` + +Triggered after `onVehicleSpawn`. + +**Arguments:** +- `wasSpawned` (boolean) - Whether the vehicle was actually spawned. +- `playerID` (number) +- `vehicleID` (number) +- `data` (string) + +**Cancellable:** no + +--- + +### `onVehicleEdited` + +Triggered when a player edits an existing vehicle. + +**Arguments:** +- `playerID` (number) +- `vehicleID` (number) +- `data` (string) - JSON string of the new configuration (does not include positional data). + +**Cancellable:** yes — returning a value other than `0` cancels the edit. + +--- + +### `postVehicleEdited` + +Triggered after `onVehicleEdited`. + +**Arguments:** +- `wasAllowed` (boolean) - Whether the edit was allowed. +- `playerID` (number) +- `vehicleID` (number) +- `data` (string) + +**Cancellable:** no + +--- + +### `onVehicleDeleted` + +Triggered when a vehicle is deleted. + +**Arguments:** +- `playerID` (number) +- `vehicleID` (number) + +**Cancellable:** no + +--- + +### `onVehicleReset` + +Triggered when a player resets a vehicle. + +**Arguments:** +- `playerID` (number) +- `vehicleID` (number) +- `data` (string) - JSON string of the new position and rotation (does not include configuration). + +**Cancellable:** no + +--- + +### `onVehiclePaintChanged` + +Triggered when a vehicle's paint is changed. + +**Arguments:** +- `playerID` (number) +- `vehicleID` (number) +- `data` (string) - JSON string with the new paint data. + +**Cancellable:** no + +--- + +### `onFileChanged` + +Triggered when a file in the plugin directory changes. + +**Arguments:** +- `path` (string) - Path of the changed file, relative to the server root. + +**Cancellable:** no + +**Note:** +Files added after the server has started are not tracked. diff --git a/docs/zh/FAQ/Defender-exclusions.md b/docs/zh/FAQ/Defender-exclusions.md new file mode 100644 index 0000000..b1979c8 --- /dev/null +++ b/docs/zh/FAQ/Defender-exclusions.md @@ -0,0 +1,52 @@ +# 如何在Windows Defender防火墙和防病毒软件中创建白名单? + +!!! info + +``` +在修改防火墙之前,请确保将windows网络设置中的网络设置为专用(假设您在私有网络中)。 + +!!! 免责声明:" + +**防火墙/防御器关闭具有风险**。 + +通过创建排除条款,您了解允许在PC上运行程序并向公众开放家庭网络端口的风险,从而使BeamMP对可能发生在您或您的家庭中的任何和所有损害负责的权利无效。 + +我们对任何外部链接服务或网站上的任何内容不承担任何责任。 +``` + +## 为 BeamMP-Launcher 添加 Windows Defender 防火墙例外规则 + +1. 打开带有高级设置的`Windows Defender防火墙。` +2. 在窗口中,单击`入站`以打开入站规则选项卡。 +3. 点击 `新建规则` 在右上方创建一个新的规则。 +4. 选择`程序`以创建特定于程序的规则。 +5. 请输入 `BeamMP-Launcher.exe` 的完整限定路径。
默认路径为 `%appdata%\BeamMP-Launcher\BeamMP-Launcher.exe`(请勿包含引号)。 +6. 确保允许连接 +7. 给排除项起一个名字(例如:“BeamMP-Launcher”)并保存它。 +8. 重启电脑 + +## 1.1 为 BeamMP-Launcher 添加 Windows Defender 防火墙例外规则 + +1. 打开 `Windows Defender高级防火墙设置`. +2. 在窗口中,点击 `入站` 以打开入站规则选项卡。 +3. 在右上角点击 `新建规则` 以创建一条新的规则。 +4. 选择 `端口` 以创建针对特定程序的规则。 +5. 输入与 ServerConfig.toml 中配置的相同的端口号。 +6. 请输入 `BeamMP-Server.exe` 的完整限定路径。
该文件位于你下载后自行放置的位置。 +7. 确保允许连接 +8. 为该排除规则命名(例如 “BeamMP-Server”),然后保存规则。 +9. 重启电脑 + +## 2. BeamMP-Launcher 与 BeamMP-Server 的 Windows Defender 病毒防护排除设置 + +1. 打开 `Windows 安全中心` 应用. +2. 单击第一个项目`病毒和威胁防护`。 +3. 单击“病毒和威胁防护设置”下方的`管理设置`。 +4. 向下滚动以导航到`排除项`选项卡。 +5. 在那里,单击“添加排除项”并选择`进程`。 +6. 在字段中输入 `BeamMP-Launcher.exe` 或 `BeamMP-Server.exe`,然后保存。 +7. 重启电脑。 + +## 还有问题吗? + +在[论坛](https://forum.beammp.com)或我们的[Discord服务器](https://discord.gg/beammp) `#support`频道中打开一个频道。 diff --git a/docs/zh/FAQ/player-faq.md b/docs/zh/FAQ/player-faq.md new file mode 100644 index 0000000..8a6d790 --- /dev/null +++ b/docs/zh/FAQ/player-faq.md @@ -0,0 +1,22 @@ +# 面向玩家的疑难解答 + +## 我如何链接我的Discord帐户? + +将你的 Discord 账号与 BeamMP 账号关联是 BeamMP 的一项新功能。
要进行此操作,请前往你的 [论坛账户偏好设置](https://forum.beammp.com/my/preferences/account),在 “关联账户(Associated Accounts)” 下连接你的 Discord 账号。(只有当论坛的双重验证(2FA)被禁用时,此选项才会显示。) + +## 我如何获得抢先体验资格? + +提前体验权限(包含紫色名牌及其他福利)可通过以下方式获取:在[Patreon](https://patreon.com/BeamMP)平台购买相应套餐、进行捐赠,或助力我们的Discord服务器。 + +## 我已订阅Patreon会员,如何领取专属福利? + +请确保您完成以下操作以自动获得您的福利: + +1. 在 [Patreon](https://www.patreon.com/settings/apps/discord) 上关联你的 Discord 账号,以在 Discord 服务器中获得相应的角色和访问权限。 +2. 请确保您在Patreon上使用的电子邮件地址与您在[论坛](https://forum.beammp.com/)上使用的BeamMP帐户的电子邮件地址相同。 + +请耐心等待,系统同步可能需要几个小时,有时长达12个小时。如果您在完成上述步骤12小时后仍未收到专属福利,请联系BeamMP支持。 + +## 我有更多的问题! + +如果您的提问或问题与游戏本体或游玩有关,请参阅[游戏常见问题解答](game-faq.md)。如果您的问题与运行服务器有关,请参阅[服务器常见问题解答](server-faq.md)。否则,请查看[论坛](https://forum.beammp.com/c/faq/35),在那里社区可以提出问题并获得答案。 diff --git a/docs/zh/beamng/dev/modding/lua-mods.md b/docs/zh/beamng/dev/modding/lua-mods.md new file mode 100644 index 0000000..6fd097f --- /dev/null +++ b/docs/zh/beamng/dev/modding/lua-mods.md @@ -0,0 +1,3 @@ +# lua-mods.md + +此页面需要创建。 diff --git a/docs/zh/community/rules.md b/docs/zh/community/rules.md new file mode 100644 index 0000000..b3da681 --- /dev/null +++ b/docs/zh/community/rules.md @@ -0,0 +1,53 @@ +--- +hide: + - 导航 +--- + +# BeamMP 社区规则 + +!!!BeamMP员工不受这些规则的严格约束,可以自行决定操作。除这些规则外,Discord的[社区指南](https://discord.com/guidelines/)和[服务条款](https://discord.com/terms/)也适用并强制执行。 + +## BeamMP通用规则(适用于Discord、论坛和游戏内) + +1. 禁止歧视(如性别歧视、种族主义等):material-information-outline:{title=“这包括直接或有意使用种族/性侮辱或其他词汇来针对个人或群体。”} +2. 禁止欺凌或骚扰:material-information-outline:{title=“这包括使用不必要的行为、未经请求的DM或使用信息、图像或口头交流针对个人。”} +3. 无NSFW、冒犯性或恶意内容:material-information-outline:{title=“这包括包含恶意链接、裸露、色情、过度亵渎或其他冒犯性、恶意和/或不适合在公共场所出现的内容。”} +4. 禁止发送垃圾邮件文本/语音渠道或帖子:material-information-outline:{title=“这包括生成多个帖子或线程,发送相同或类似的消息,滥用语音通信渠道或其他行为,包括加入/离开垃圾邮件。”} +5. 请说英语:material-information-outline:{title=“在可能的情况下,BeamMP内部的所有通信都应使用英语,除非另有指定的特定区域。”} +6. 禁止不适当的个人资料:material-information-outline:{title=“这包括个人资料名称,照片,bios或其他不符合BeamMP/BeamNG/Discord规则和ToS的用户控制内容。”} +7. material-information-outline:{title=“禁止发布个人信息(” doxing ")(包括但不限于真实姓名、地址、电子邮件、密码、银行账户和信用卡信息等)。} +8. material-information-outline:{title=“这包括讨论BeamMP/BeamNG或整个游戏之外的过去或当前世界事件。有一些地方可以对这些话题进行有效的讨论,但BeamMP不是其中之一。”} +9. 非BeamMP/BeamNG内容:material-information-outline:{title="这包括非BeamMP社区、社交媒体渠道或其他外部链接。这适用于在BeamMP社区内发帖,以及以个人广告为目的主动向用户发送DM。”} +10. 禁止冒充工作人员:material-information-outline:{title="这包括声称自己是BeamMP的工作人员,或声称拥有与工作人员相关的能力。这也包括模仿工作人员。”} +11. 禁止利用:material-information-outline:{title=“这包括利用任何漏洞或疏忽来谋取个人利益或对他人的体验产生负面影响。”} +12. 禁止分享 Authkey,禁止用小号刷免费密钥数量,违者永久封禁。 +13. 遵循所有给定的TOS:material-information-outline:{title=“这很重要,因为使用我们的Discord服务器也必须遵循诸如Discord年龄限制(13+)之类的限制。”} +14. 尊重每个人:material-information-outline:{title=“有些话不该说出口,你需要尊重你周围的每个人,并相应地行动。”} +15. 关注类别或频道的主题。:material-information-outline:{title="这应该不说出来,但要保持你的内容在你发布的区域的主题上。例如,支持应该帮助人们或在寻求支持时使用。”} +16. 不要使用过多的大写锁定或表情符号:material-information-outline:{title=“用户应该避免使用过多的大写符号或表情符号/表情符号,以免混淆手头的重点。”} +17. 在支持区域发布之前,请检查[BeamMP FAQ](../../support/player-faq.md) & [BeamMP社区FAQ](https://forum.beammp.com/c/faq/35)。 + +## Discord 规则 + +1. 以上规则适用于这里:material-information-outline:{title=“本文档中提到的所有规则适用于语音聊天。”} +2. 遵循Discord的使用条款和指南:material-information-outline:{title=“遵循Discord在其使用条款和指南中列出的所有规则”} +3. 禁止NSFW音频/流:material-information-outline:{title=“玩家不应该流任何违反BeamMP规则和Discord TOS的内容。”} +4. 上述规则包括赌博服务/网站。 +5. material-information-outline:{title=“禁止过度使用响板、大声播放音乐或使用其他音频通道故意煽动或针对他人。”} +6. 禁止加入/离开滥用:material-information-outline:{title=“大多数用户可能启用声音效果,不要快速加入和离开语音聊天。”} +7. 不要纠缠他人(`@'ing`):material-information-outline:{title=“这包括在被忽视或拒绝后反复要求他人回应。”} +8. 将bot命令保存在特定于命令的通道中。 +9. 禁止角色扮演:material-information-outline:{title=“这是为了防止由于缺少上下文或防止用户规避某些规则而引起的问题。”} +10. 请勿要求用户通过私信(DM)向你寻求帮助。所有与支持相关的话题均应在 BeamMP Discord 服务器的指定支持频道中进行讨论。 +11. 严禁发送任何可疑的、潜在恶意或具有破坏性的文件及附件(包括但不限于可疑扩展名)。 +12. 不要教/发链接教别人怎么盗版、破解软件、骇入账号、破解付费mod。 +13. 不要发布非BeamNG /BeamMP内容。只允许在专用的[媒体通道](https://discord.com/channels/601558901657305098/705427325646274680)中发送您自己通道的媒体。 +14. 听从工作人员的指示。如果你认为某位员工滥用职权,直接通知他们的领导或项目管理人员。 + +## 如何对您被实施的封禁进行申诉: + +### 封禁上诉可在封禁上诉- BeamMP提交。 + +所有禁令申诉都经过彻底调查,涉及至少两(2)名与本案无关的工作人员/审核小组成员。发出申诉的工作人员将被允许发表评论,但不会以其他方式参与申诉决定。这些上诉结果是最终的。BeamMP保留根据BeamMP社区规则(如上所述)、[Discord ToS](https://discord.com/terms)和其他因素允许或不允许成员的权利。 + +!!! 如果对您采取行动,您将被禁止创建新的Discord或BeamMP帐户来规避禁令。 diff --git a/docs/zh/game/getting-started.md b/docs/zh/game/getting-started.md index 113cc57..618086a 100644 --- a/docs/zh/game/getting-started.md +++ b/docs/zh/game/getting-started.md @@ -1,39 +1,147 @@ # 入门 ---- +## **1. 兼容性** -## **开始之前** +BeamMP与Windows和Linux完全兼容,与MacOS的兼容性正在研究中。然而,Linux和MacOS都是次要平台,这意味着bug在所难免。 -BeamMP仅与Steam正版的游戏版本兼容。不支持”盗版“版本。 +!!!警告 ---- +``` +BeamMP将无法与盗版或过时版本的BeamNG.drive一起工作。 +BeamMP支持团队不提供盗版/过期副本问题的支持。 +``` -## **安装** +## **2. 安装** -BeamMP目前仅与Windows系统原生兼容。 - -### **Windows安装** +### **2a. Windows 安装** 1. 访问[beammp.com](https://beammp.com/)并点击“Download Client”按钮。 2. 解压`BeamMP_Installer.zip`。 3. 运行`BeamMP_Installer.exe`并按照说明进行操作。 4. BeamMP启动器图标应将出现在您的桌​​面上。如果没有,只需在Windows搜索栏中搜索“BeamMP”即可。 5. 启动器运行后,您应该会看到一个终端窗口,随即 BeamNG.drive将自动运行。**请勿**关闭终端窗口。 -6. BeamNG启动后,在主菜单中点击`联机模式`即可开始多人游戏。 -7. 系统将提示您登录或以访客身份游玩(并非所有服务器都允许访客)。您可以在我们的[论坛](https://forum.beammp.com)上创建一个帐户,然后使用相同的凭据登录BeamMP。 -8. 选择您感兴趣的任何服务器,然后点击`连接` 。尽情享受吧! +6. BeamNG启动后,在主菜单中点击`Repository`按钮,确保`multiplayerbeammp`是**唯一启用**的mod。 +7. 返回主菜单,点击“More..”和“多人模式”按钮开始多人模式。 +8. 系统将提示您登录或以访客身份游玩(并非所有服务器都允许访客)。您可以在我们的[论坛](https://forum.beammp.com)上创建一个帐户,然后使用相同的凭据登录BeamMP。 +9. 选择您感兴趣的任何服务器,然后点击`连接` 。尽情享受吧! -注意:*当您加载到已有多辆载具的地图时,加入可能需要比预期更长的时间。* +!!!注意 -### **Linux安装** +``` +当你加载到一个有多个车辆的地图时,它可能需要比预期更长的时间才能加入。 +``` -请参考[英语文档](https://docs.beammp.com/game/getting-started/#2b-linux-installation) +### **2b. Linux 安装** + +目前您需要自己编译Launcher。为了做到这一点,您需要对如何编译应用程序有一个基本的了解。 + +确保你已经安装了基本的开发工具,通常可以在包中找到,例如: + +- Debian/Ubuntu: `sudo apt install build-essential` +- Fedora: `sudo dnf install cmake gcc gcc-c++ make perl perl-IPC-Cmd perl-FindBin perl-File-Compare perl-File-Copy kernel-headers kernel-devel` +- Arch: `sudo pacman -S base-devel` +- openSUSE: `zypper in -t pattern devel-basis` +- SteamOS (Arch): `sudo pacman -S base-devel linux-api-headers glibc libconfig` (You also need to do `sudo steamos-readonly disable` but make sure to enable it again after installing the packages) + +克隆 `vcpkg` 仓库,执行引导脚本并加入系统路径 + +1. + +```bash +git clone https://github.com/microsoft/vcpkg.git +``` + +1. + +```bash +./vcpkg/bootstrap-vcpkg.sh +``` + +1. + +```bash +export VCPKG_ROOT="$(pwd)/vcpkg" +export PATH=$VCPKG_ROOT:$PATH +``` + +使用`git`将BeamMP-Launcher仓库克隆至本地,操作示例如下:`git clone https://github.com/BeamMP/BeamMP-Launcher.git`。[查看GitHub仓库克隆操作完整指南](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) + +检出[最新发布版本](https://github.com/BeamMP/BeamMP-Launcher/releases/latest)所使用的标签。例如,若最新版本使用`v2.6.4`,则执行`git checkout v2.6.4` + +如果你使用了我们提供的示例克隆命令,你可以使用以下命令进入项目的根目录:
`cd BeamMP-Launcher` + +在项目的根目录中, + +1. + +```cmake +cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux +``` + +1. + +```cmake +cmake --build bin --parallel +``` + +!!!注意 "" + +``` +如果在编译时内存耗尽,可以提交——parallel指令,由于只在一个CPU线程上编译,它将使用更少的RAM。 +``` + +!!!注意 "" + +``` +通过不指定‘ -DCMAKE_BUILD_TYPE=Release ’,您正在编译一个调试版本,该版本的文件大小较大,但不包含launcher-can-only-connect-to- server-once错误 +``` + +!!!注意 "Fedora 用户" 如果 vcpkg 在编译 OpenSSL 时因内核头文件(kernel headers)错误而失败,请确保已安装所有依赖项: `bash sudo dnf install kernel-headers kernel-devel gcc gcc-c++ make perl ` 然后清理 vcpkg 缓存:`bash rm -rf $VCPKG_ROOT/buildtrees/openssl `最后重新运行 cmake 配置命令。 + +将已编译完成的应用程序从`/bin`移出,放到它自己的文件夹中,然后从该文件夹运行它: + +```bash +mkdir -p ~/beammp-launcher +cp bin/BeamMP-Launcher ~/beammp-launcher/ +cd ~/beammp-launcher +./BeamMP-Launcher +``` + +原生的 Linux 版 BeamMP 启动器将会启动,并使用原生的 Linux 版 BeamNG.drive。 + +### **2c. 在Proton兼容层上游玩BeamNG.drive** + +若您需要在Proton环境运行BeamNG.drive时同步使用原生Linux版BeamMP启动器,可通过以下方案实现: + +使用`--no-launch`参数运行BeamMP-Launcher(此参数将阻止启动器唤起原生Linux版BeamNG.drive)。更多启动器参数详解请参阅[开发环境配置指南](../guides/beammp-dev/beammp-dev.md) + +将Proton-BeamNG.drive的用户文件夹路径指向Linux-BeamNG.drive的位置(因原生Linux版BeamMP-Launcher当前仅支持向Linux-BeamNG.drive用户文件夹写入数据) + +例如,您可以通过创建符号链接实现该配置 + +- 记录Linux-BeamNG.drive用户文件夹路径(通常位于`~/.local/share/BeamNG.drive`),并将其重命名为类似`BeamNG.drive_old`的名称 +- 记录Proton-BeamNG.drive用户文件夹路径(通常位于`~/.local/share/Steam/steamapps/compatdata/284160/pfx/drive_c/users/steamuser/AppData/Local/BeamNG.drive`) +- 建立双端用户文件夹的符号链接桥接 `ln -s ~/.local/share/Steam/steamapps/compatdata/284160/pfx/drive_c/users/steamuser/AppData/Local/BeamNG.drive ~/.local/share` + +当用户文件夹间符号链接就绪且启动器完成编译后,可通过调整Steam启动参数实现:既让Steam通过Proton运行游戏,又能自动触发启动器执行。请前往Steam库中该游戏的属性设置界面,替换原版游戏启动参数为下方配置模板: + +- `~/BeamMP/BeamMP-Launcher --no-launch & %command% ; killall BeamMP-Launcher` + +请注意:此配置默认假设您已将编译完成的启动器二进制文件存放于`/home/user/BeamMP/`。请根据实际二进制文件存放路径调整配置,且每次启动器版本更新时需切换至官方Git仓库对应分支重新执行编译流程。 + +!!! 提示 "安装表情符号字体实现文本内嵌表情" + +``` +若需在服务器列表(作为服务器自定义名称组成部分)或游戏内聊天中显示表情符号,您需部署包含表情符号的字形库。例如,可通过安装Windows Segoe UI 表情符号字体的 Linux 移植版实现。 +``` --- -## **已知问题** +## **3. 已知的问题** -- 如果您未看到”联机模式“按钮。确保BeamMP模组已在“模组管理器”中激活,然后尝试按下CTRL+L。 -- 另外,请确保BeamMP是“模组管理器”中**唯一**激活的模组,否则可能会导致问题。 +- 当前原生Linux版BeamMP启动器存在单会话限制:成功连接服务器并断开后需重启启动器。可通过热重载方案实现不关闭游戏进程的快速重启 +- 若未显示多人游戏按钮,请确认以下操作:
检查BeamMP模组是否已安装并在模组管理器中启用
尝试执行热重载快捷键 Ctrl + L +- 任何类型的vpn都可能导致连接问题。 +- 如果启动器报告任何错误,请阅读[FAQ](https://forum.beammp.com/c/faq/35)。 -如果您需要更多安装帮助,欢迎您在我们的[论坛](https://forum.beammp.com)上发帖或在我们的[Discord服务器](https://discord.gg/beammp)上提问(仅英文)。 +如果启动器报告任何错误,请阅读[FAQ](https://forum.beammp.com)。 diff --git a/docs/zh/guides/beammp-dev/beammp-dev.md b/docs/zh/guides/beammp-dev/beammp-dev.md new file mode 100644 index 0000000..a15176a --- /dev/null +++ b/docs/zh/guides/beammp-dev/beammp-dev.md @@ -0,0 +1,75 @@ +# 开始 + +为了开始开发BeamMP,你至少需要: + +- 安装在本地的BeamNG.drive +- 安装在本地的BeamMP; 至少是启动器,另外还有服务器 +- 安装在本地的Git, 和一个 GitHub.com 账号 +- 一个代码编辑器, 例如VSCode或notepad++ + +--- + +# 模组,启动器和服务器之间的区别 + +BeamMP分为三个主要部分: + +- 该模组与游戏中的其他载具模组或界面模组相同,均通过BeamNG引擎加载运行。其核心功能包括与启动器建立本地通信链路,以及渲染多人联机界面组件。技术架构方面,主体逻辑采用Lua脚本语言实现,界面层则基于JavaScript、HTML与CSS的复合式开发框架进行构建。相关代码仓库详见[https://github.com/BeamMP/BeamMP](https://github.com/BeamMP/BeamMP)。 +- 本启动器核心功能集包括:与模组维持持续通信、在需要时建立目标服务器连接,以及通过BeamMP后端系统执行用户登录认证流程。该程序基于C++语言开发,部署前已由BeamMP团队完成预编译处理,完整源代码参见[https://github.com/BeamMP/BeamMP-Launcher](https://github.com/BeamMP/BeamMP-Launcher)。 +- 服务端核心功能涵盖:与多个启动器实例建立通信链路、向BeamMP后端系统发送"心跳"信号同步IP/端口/版本/玩家数量等运行参数,并管理与调度服务端Lua插件运行。底层架构基于C++语言实现,已由BeamMP针对多操作系统平台及CPU指令集进行预编译生成,完整源码仓库位于[https://github.com/BeamMP/BeamMP-Server](https://github.com/BeamMP/BeamMP-Server)。 + +--- + +# 配置适用于模组开发的运行环境 + +## 为BeamNG使用未打包文件 + +为实现BeamNG模组开发的高效迭代,建议开发者采用`非打包目录`的实时更新工作流模式,避免每次代码变更后执行重复的压缩打包操作。 + +请通过文件管理器访问BeamNG用户目录:`%appdata%/Local/BeamNG.drive/0.xx/mods`,其中`xx`对应当前最新的BeamNG版本号。在`mods`目录内创建名为`unpacked`的专用开发文件夹。 + +有关用户目录的技术规范细则,请参阅BeamNG官方文档中心:[https://documentation.beamng.com/support/userfolder/](https://documentation.beamng.com/support/userfolder/) + +## 在BeamMP启动器中启用开发模式 + +为防止自动更新机制覆盖本地Git仓库副本,请通过添加运行参数`--no-download`强制停用该功能;若需同时禁止启动器自动运行BeamNG主程序,并获取实时调试日志,推荐配置方案为附加开发调试参数`--dev`。 + +命令行参数 | 说明 +:-- | :-- +`--help` or `-h` | 该指令将输出下列标准化参数清单 +`--port ` or `-p` | 请将服务端默认监听端口修改为``,该项变更需同时在游戏客户端内进行同步配置。 +`--verbose` or `-v` | 详细日志模式,输出调试级系统信息 +`--no-download` | 强制禁用BeamMP Lua模组自动部署流程 +`--no-update` | 激活维护模式锁定更新通道(需采用手动升级方式) +`--no-launch` | 强制解耦启动器与游戏进程链路(需进行独立启动流程) +`--dev` | 开发者模式,与 --verbose --no-download --no-launch --no-update相同 +`--game ` or `-- ` | 实现运行参数向游戏进程的透传桥接 + +## 执行仓库克隆操作至目标解压目录 + +虽然您可手动从GitHub仓库(a0)https://github.com/BeamMP/BeamMP复制BeamMP模组文件,但强烈建议使用Git版本控制系统进行规范操作。请首先执行分叉(fork)操作。 + +最有效的方式是直接将存储库克隆到`unpacked`文件夹中。 + +在`git`环境配置下,请遵循以下部署流程:
从`解压根目录`启动PowerShell/CMD终端
执行克隆操作:`git clone https://github.com/yourName/BeamMP` + +现在试试开发模式吧。启动BeamMP启动器,手动启动BeamNG,一旦进入游戏,确保BeamMP是唯一有效的模组。你应该能够像往常一样使用BeamMP。 + +使用代码编辑器,您现在可以直接在`unpacked`文件夹中添加或更改代码。然后你可以通过按`Ctrl+L`重新加载Lua来尝试更改(如果你做了UI更改,则按`F5`)。 + +一旦您对更改满意,就可以通过git提交它们。有关如何使用Git的教程和文档,请参阅Git- scm网站。一旦您的更改被提交并推送(到您的分支),您就可以发出拉取请求。 + +如果您遇到任何问题,请在[Discord](https://discord.gg/beammp)的#scripting频道中提问。 + +--- + +# 设置本地服务器 + +在使用BeamMP时,使用本地服务器可能是有益的。您可以遵循常规的[服务器安装](../../server/create-a-server.md),同时省略纯本地连接的前两个步骤。 + +在`serverConfig`中将服务器设置为私有。使用任意字符串作为`AuthKey`。 + +--- + +# 贡献指南 + +有关代码格式、提交信息格式、通用开发最佳实践等详细信息,请参阅各仓库中的`CONTRIBUTING.md`文件。该文件包含更详细的贡献指南,各仓库中的`README.md`文件通常也会提供具体的构建步骤说明(针对需要编译的项目)。 diff --git a/docs/zh/guides/mod-creation/server/getting-started.md b/docs/zh/guides/mod-creation/server/getting-started.md new file mode 100644 index 0000000..29a2960 --- /dev/null +++ b/docs/zh/guides/mod-creation/server/getting-started.md @@ -0,0 +1,101 @@ +# 创建多人模式 + +## 文件夹结构和基础文件 + +基本的文件夹和文件结构应该是这样的: + +``` +Resources/ +├─ Client/ +│ └─ examplePlugin.zip/ +│ ├─ scripts/ +│ │ └─ modScript.lua +│ └─ lua/ +│ └─ ge/ +│ └─ extensions/ +│ └─ examplePlugin.lua +└─ Server/ + └─ examplePlugin/ + ├─ examplePlugin.lua + └─ further_lua/ + └─ further.lua +``` + +服务器端lua是最基本的,如果您想添加自定义事件,您还至少需要一个客户端lua以及一个modscript.lua + +Server目录需创建多个子文件夹存放各服务端模组。推荐只在根目录保留一个主Lua文件,新增的Lua文件可归类到对应子目录。实际使用中不必严格遵循此规范,若同一层级存在多个Lua文件时,服务器会按文件名称的字母表顺序自动加载。 + +Client目录专门存放将发送给客户端的ZIP格式模组包,当客户端接收后会自动以模组形式加载。该目录若存在其他类型文件会导致服务器启动时报错(但不会阻断服务运行),不过这些无关文件仅会被服务端忽略。核心配置脚本modScript.lua由BeamNG游戏引擎解析,用于指引游戏加载指定插件模块。 + +!!!范例 "" [Download the examplePlugin.zip](../../../../assets/content/ResourcesForExamplePlugin.zip) + +## 服务器端 lua + +更多完整示例可参考examplePlugin模块的实现,现提供一个基础调试示例,实例的功能是显示玩家身份标识 + +```lua +function onInit() --runs when plugin is loaded + + MP.RegisterEvent("onPlayerAuth", "onPlayerAuth") --Provided by BeamMP + + print("examplePlugin loaded") +end + +--A player has authenticated and is requesting to join +--The player's name (string), forum role (string), guest account (bool), identifiers (table -> ip, beammp) +function onPlayerAuth(player_name, role, isGuest, identifiers) + local ip = identifiers.ip + local beammp = identifiers.beammp or "N/A" + print("onPlayerAuth: player_name: " .. player_name .. " | role: " .. role .. " | isGuest: " .. tostring(isGuest) .. " | identifiers: ip: " .. ip .. " - beammp: " .. beammp) +end +``` + +`onPlayerAuth`会在玩家尝试加入时触发,详细信息请参考脚本文档中的[onPlayerAuth条目](../../../scripting/server/latest-server-reference/#onplayerauth) + +以下是使用onPlayerAuth的另一个示例,该示例将通过向客户端返回信息来禁止游客加入服务器,返回的信息会直接显示给玩家: + +```lua +function onPlayerAuth(playerName, playerRole, isGuest, identifiers) + if isGuest then + return "No guests allowed, please use a BeamMP account" + end +end +``` + +关于BeamMP服务端函数的详细信息请查阅[最新版服务端技术文档](../../../scripting/server/latest-server-reference.md) + +## 客户端 lua + +该实现主要遵循[BeamNG扩展模块](https://documentation.beamng.com/modding/programming/extensions/)的技术规范。 + +```lua +local M = {} + +if extensions.isExtensionLoaded("examplePlugin") then + log("E", "examplePlugin", "examplePlugin loaded on client side") + return +end + +return M +``` + +终端输出提示:examplePlugin已完成加载。 + +建议查阅[BeamNG调试输出官方文档](https://documentation.beamng.com/modding/programming/debugging/#a-add-a-log)获取详细说明 + +## modScript.lua + +通常只包含两行 + +```lua +load('examplePlugin') +setExtensionUnloadMode('examplePlugin', 'manual') +``` + +如果你想在日志中看到你的modScript被BeamNG处理,你可以添加一个日志打印 + +```lua +load('examplePlugin') +setExtensionUnloadMode('examplePlugin', 'manual') +log('I', 'modScript', "examplePlugin loaded") +``` diff --git a/docs/zh/scripting/mod-reference.md b/docs/zh/scripting/mod-reference.md new file mode 100644 index 0000000..40c032d --- /dev/null +++ b/docs/zh/scripting/mod-reference.md @@ -0,0 +1,45 @@ +!!! 警告 "这个网站正在建设中!" + +``` +这个网站正在积极建设中。 + +觉得你能帮上忙吗?请用铅笔在右侧点击页面! + +这也可以在任何页面上完成。 +``` + +# 模组/游戏内脚本参考 + +BeamMP允许您创建自己的客户端插件。我们提供了一些功能,你可以用它来与其他多人模式和其他玩家通过服务器进行通信。 + +# 函数列表 + +脚本可用函数列表: + +函数 | 说明 +--- | --- +`TriggerServerEvent("eventName", "data")` | 在服务器lua环境中触发事件,两个参数都是字符串。 +`TriggerClientEvent("eventName", "data")` | 在本地lua环境中触发事件,两个参数都是字符串。有利于插件之间的通信。 +`AddEventHandler("eventName", Function)` | 当`eventName`被接收时(本地或从服务器),`function`将获得1个参数,一个包含事件数据的字符串。 + +# 代码片段 + +例如,使用包含的`ChatMessageIncluded`事件解析聊天,如下所示: + +```lua +local function chatReceived(msg) -- Receive event with parameters + print("chat received: "..msg) + local i = string.find(s, ":") -- Find where our first ':' is, used to separate the sender and message + if i == nil then + print("error parsing message: separator could not be found!") + return -- Could not find separator, cancel function + end + print("index of separator: "..tostring(i)) + local sender = string.sub(msg, 1, i-1) -- Substring our input to separate its 2 parts + local message = string.sub(msg, i+1, -1) -- Do whatever you want to with the message + print("sender: " .. sender) + print("message: ".. message) +end + +AddEventHandler("ChatMessageReceived", chatReceived) -- Add our event handler to the list managed by BeamMP +``` diff --git a/docs/zh/scripting/server/latest-server-reference.md b/docs/zh/scripting/server/latest-server-reference.md new file mode 100644 index 0000000..2b4d367 --- /dev/null +++ b/docs/zh/scripting/server/latest-server-reference.md @@ -0,0 +1,1319 @@ +!!! 警告 "这个网站正在建设中!" + +``` +这个网站正在积极建设中。 + +觉得你能帮上忙吗?请用铅笔在右侧点击页面! + +这也可以在任何页面上完成。 +``` + +# 服务器脚本参考 + +## 服务器版本 3.X + +### 介绍 + +BeamMP-Server v3.0.0版本对Lua插件系统的工作方式做了一些重大的改变。没有办法在新服务器上使用旧的lua,因此您必须进行迁移。 + +服务器的插件系统使用[Lua 5.3](https://www.lua.org/manual/5.3/)。本节详细介绍了如何开始编写插件,教一些基本概念,并让您开始使用第一个插件。即使您了解v3.0.0之前的系统,也建议您阅读本节,因为现在发生了一些巨大的变化。 + +有关从v3.0.0之前版本的lua迁移指南,请转到[“从旧lua迁移”](#migrating-from-old-lua)一节。 + +### 目录结构 + +服务器插件(Server plugins)与模组(mods)的默认安装路径存在差异:前者默认位于<code>Resources/Server</code>目录下,而专为BeamNG.drive编写且需同步至客户端的模组则存放于<code>Resources/Client</code>路径。每个插件必须在<code>Resources/Server</code>目录下拥有独立子文件夹,例如名为"MyPlugin"的插件应具备如下结构: + +``` +Resources +└── Server + ├── MyPlugin + │ └── main.lua + └── SomeOtherPlugin + └── ... +``` + +为演示<code>Resources/Server</code>目录如何管理多插件共存的情况,此处同时展示名为"SomeOtherPlugin"的另一插件配置。本指南将持续以该目录结构作为操作基准示例,所有代码片段及配置文件均基于此层级关系展开说明。 + +您会注意到核心配置文件<code>main.lua</code>的存在。开发者可自由创建多个<code>.lua</code>扩展名的脚本文件(建议遵循<a href="https://github.com/BeamMP/BeamMP-Server/wiki/Scripting-Guide">BeamMP脚本规范</a>),所有位于插件主目录层级的Lua文件将按<strong>字母表顺序</strong>初始化(如<code>aaa.lua</code>优先于<code>bbb.lua</code>执行)。 + +### Lua 文件 + +插件目录中的每个<code>.lua</code>文件都会在服务器启动时加载。这意味着所有函数外部的语句会被立即解析执行(即"运行"),该行为由BeamMP服务端核心的Lua虚拟机在初始化阶段强制触发。 + +子目录中的Lua文件不会被自动加载,但可通过`require()`函数手动调用。 + +例如,`main.Lua `是这样的: + +```lua +function PrintMyName() + print("I'm 'My Plugin'!") +end + +print("What's up!") +``` + +当服务器启动时,`main.lua`会启动,它会把`print("What's up!")`*运行*,但是**不会** *调用* `PrintMyName`函数(因为它没有被调用)! + +### 事件 + +事件类似于“玩家加入”,“玩家发送聊天信息”,“玩家生成车辆”。 + +您可以通过从处理程序返回`1`来取消事件(如果它们是可取消的)。 + +在Lua中,您通常希望对其中的一些做出反应。为此,您可以注册一个“处理程序”。这是一个在事件发生时调用的函数,并传递一些参数。 + +范例: + +```lua +function MyChatMessageHandler(sender_id, sender_name, message) + -- censoring only the exact message 'darn' + if message == "darn" then + -- cancel the event by returning 1 + return 1 + else + return 0 + end +end + +MP.RegisterEvent("onChatMessage", "MyChatMessageHandler") +``` + +这将有效确保任何与"darn"完全匹配的信息既不会被发送也不会出现在聊天中(注意:实际脏话过滤器应检测消息*是否包含*"darn"而非*完全等于*)。取消事件会阻止其发生,例如使聊天信息不向其他玩家显示,载具无法生成等情形。 + +### 自定义事件 + +您可以注册任何您喜欢的事件,例如: + +```lua +MP.RegisterEvent("MyCoolCustomEvent", "MyHandler") +``` + +您可以触发这些自定义事件: + +```lua +-- call all event handlers to this in ALL plugins +MP.TriggerGlobalEvent("MyCoolCustomEvent") +-- call all event handlers to this in THIS plugin +MP.TriggerLocalEvent("MyCoolCustomEvent") +``` + +您可以使用事件做更多的事情,但是这些可能性将在下面的API参考中详细介绍。 + +### 事件计时器 ("线程") + +在v3.0.0之前,Lua有一个每秒运行X次的“线程”概念。这个命名有点误导人,因为它们是同步的。 + +v3.0.0 Lua改为“事件计时器”。这些是在服务器内部运行的计时器,一旦它们用完,它们就会触发一个事件(全局)。这也是同步的。请注意,第二个参数是以毫秒为单位的间隔。 + +范例: + +```lua +local seconds = 0 + +function CountSeconds() + seconds = seconds + 1 +end + +-- create a custom event called 'EverySecond' +-- and register the handler function 'CountSeconds' to it +MP.RegisterEvent("EverySecond", "CountSeconds") + +-- create a timer for this event, which will fire every 1000ms (1s) +MP.CreateEventTimer("EverySecond", 1000) +``` + +这将导致“CountSeconds”每秒被调用一次。您还可以取消使用事件计时器`MP.CancelEventTimer`(参见API参考)。 + +从服务器的控制台中,您可以运行`status`来查看当前正在运行的事件计时器的数量,以及正在等待的事件处理程序的信息。该命令将在将来显示更多信息。 + +### 调试 + +Lua很难调试。遗憾的是,像`gdb`这样的工业级调试器并不存在于嵌入式Lua中。 + +通常,您当然可以在任何时候简单地`print()`您想要检查的值。 + +在v3.0.0中,服务器为您提供了一种将解释器注入插件并随后在其中实时运行Lua的最接近调试器的方法。 + +假设你有上面我们称为`MyPlugin`的插件,你可以像这样进入它的Lua状态: + +``` +> lua MyPlugin +``` + +字母大小写在此处至关重要,请确保输入准确无误。最终输出结果需严格遵循既定格式要求。 + +``` +lua @MyPlugin> +``` + +如你所见,我们已切入`MyPlugin`的Lua运行环境。自当前操作时点起,至执行`exit()`指令前(v3.1.0版本后变更为`:exit`),系统将全程驻留于`MyPlugin`模块,在此状态下可执行Lua脚本操作。 + +例如,如果我们有一个名为`MyValue`的全局变量,我们可以像这样输出该值: + +``` +lua @MyPlugin> print(MyValue) +``` + +你可以在这里调用函数,做任何你想做的事情。 + +从v3.1.0开始:你可以按TAB键来自动完成函数和变量。 + +警告:不幸的是,如果Lua状态当前正忙于执行其他代码(如`while`循环),这可能会使控制台完全挂起,直到它完成这项工作,所以切换到可能正在等待某些事情发生的状态时要非常小心。 + +此外,您可以在常规控制台(`> `)中运行`status`,这将向您显示有关Lua的一些统计信息。 + +### 自定义命令 + +为了实现服务器控制台的自定义命令,可以使用事件`onConsoleInput`。当你想为服务器所有者添加一种向插件发送信号的方式,或者以自定义方式显示内部状态时,这可能很有用。 + +这里有一个范例: + +```lua +function handleConsoleInput(cmd) + local delim = cmd:find(' ') + if delim then + local message = cmd:sub(delim+1) + if cmd:sub(1, delim-1) == "print" then + return message + end + end +end + +MP.RegisterEvent("onConsoleInput", "handleConsoleInput") +``` + +这将使您能够在服务器的控制台中执行以下操作: + +``` +> print hello, world +hello, world +``` + +我们实现了自己的`print`。作为练习,尝试构建一个像`say`这样的函数,它将聊天消息发送给所有玩家,甚至是特定的玩家(使用`MP.SendChatMessage`)。 + +**注意:**对于你自己的插件,通常建议给它们“namespace”。我们的`print`示例,在一个名为`mystuff`的插件中,可以称为`mystuff.print`或`ms.print`或类似内容。 + +### API 参考 + +文档格式: `function_name(arg_name: arg_type, arg_name: arg_type) -> return_types` + +### 内键指令功能 + +#### `print(...)`, `printRaw(...)` + +将消息输出到服务器控制台,前缀`[DATE TIME] [LUA]`. 如果你不想要这个前缀, 你可以使用`printRaw(…)`。 + +范例: + +```lua +local name = "John Doe" +print("Hello, I'm", name, "and I'm", 32) +``` + +它可以接受任意类型的任意多参数。它也会开心的转储表! + +它的行为类似于lua解释器的`print`,所以它会在参数之间放置制表符。 + +#### `exit()` + +正常关闭服务器。触发`onShutdown`事件。 + +### MP 功能 + +#### `MP.CreateTimer() -> Timer` + +创建一个计时器对象,该对象可用于跟踪某事花费了多长时间/经过了多少时间。它一旦创建就会启动,并且可以使用`mytimer:Start()`来重置/重新启动。 + +您可以使用`mytimer:GetCurrent()`获取当前经过的时间(以秒为单位)。 + +范例: + +```lua +local mytimer = MP.CreateTimer() +-- do stuff here that needs to be timed +print(mytimer:GetCurrent()) -- print how much time elapsed +``` + +计时器不需要停止(也不能停止),它们没有性能开销。 + +#### `MP.GetOSName() -> string` + +返回当前操作系统的名称,`Windows`, `Linux`或`Other`。 + +#### `MP.GetServerVersion() -> number,number,number` + +以主要、次要、补丁格式返回当前服务器版本。例如,v3.0.0版本将返回` 3,0,0 `。 + +范例: + +```lua +local major, minor, patch = MP.GetServerVersion() +print(major, minor, patch) +``` + +输出: + +``` +2 4 0 +``` + +#### `MP.RegisterEvent(event_name: string, function_name: string)` + +将名称为`Function Name`的函数注册为`Event Name`事件对应处理器,完成事件与回调逻辑的映射关联操作。 + +您可以根据需要为一个事件注册任意多个处理程序。 + +有关服务器提供的事件列表,请参阅[这里](#events-1)。 + +如果具有该名称的事件不存在,则创建它,因此RegisterEvent不会失败。这可用于创建自定义事件。更多信息请参见[自定义事件](#custom-events)和[事件](#events)。 + +范例: + +```lua +function ChatHandler(player_id, player_name, msg) + if msg == "hello" then + print("Hello World!") + return 0 + end +end + +MP.RegisterEvent("onChatMessage", "ChatHandler") +``` + +#### `MP.CreateEventTimer(event_name: string, interval_ms: number, [strategy: number (since v3.0.2)])` + +在服务器内部启动定时器,触发事件`event_name`每`interval_ms`毫秒。 + +事件计时器可以取消 `MP.CancelEventTimer`. + +不建议间隔小于25毫秒,因为多个这样的间隔可能无法及时可靠地提供服务。虽然可以在同一事件上启动多个计时器,但建议创建尽可能少的事件计时器。例如,如果您需要一个每半秒运行一次的事件和一个每秒钟运行一次的事件,可以考虑只创建一个每半秒运行一次的事件,并运行每秒钟运行一次的functionosecond触发器。 + +您也可以使用`MP.CreateTimer`来创建一个计时器并测量自上次事件调用以来经过的时间,以尽量减少事件计时器,尽管不一定建议这样做,因为它会显着增加代码复杂性。 + +**自 3.0.2 起:** + +可选的`CallStrategy`可以作为第三个参数提供。可以是以下任一值: + +- `MP.CallStrategy.BestEffort` (默认):将尝试以指定的时间间隔触发事件,但如果处理程序花费的时间太长,将拒绝排队处理程序。 +- `MP.CallStrategy.Precise` :将按照指定的精确间隔将事件处理程序入队。如果处理程序执行时间超过该间隔,则可能导致队列填满。仅在需要精确间隔时使用。 + +#### `MP.CancelEventTimer(event_name: string)` + +取消名称为`event_name`的事件上的所有计时器在某些情况下,由于异步编程的性质,计时器可能在被取消之前再次关闭。 + +#### `MP.TriggerLocalEvent(event_name: string, ...) -> table` + +本地插件同步事件触发器。 + +在本地触发一个事件,这将导致*当前Lua状态*(通常是当前插件,除非通过PluginConfig.toml共享了状态)中注册该事件的所有处理函数被调用。 + +你可以传递参数给这个函数(`…`),这些参数被复制并作为函数参数发送给所有的处理程序。 + +此调用是同步的,并将在所有事件处理程序完成后返回。 + +返回的值是一个包含所有结果的表。如果某个处理程序返回了值,它就会出现在这个表中,未加注释且未命名。这可用于“收集”东西,或者为可取消的事件注册子处理程序。实际上,这就像一个数组。 + +范例: + +```lua +local Results = MP.TriggerLocalEvent("MyEvent") +print(Results) +``` + +#### `MP.TriggerGlobalEvent(event_name: string, ...) -> table` + +全局异步事件触发器。 + +全局触发一个事件,导致所有插件(包括*this*插件)中该事件*的所有处理程序被调用。* + +您可以向此函数(`...`)传递参数,这些参数会被复制并作为函数参数发送给所有处理程序。 + +此调用是异步的,并返回一个类似未来对象的值。本地处理程序(与调用者处于同一插件中的处理程序)会同步且立即运行。 + +返回的表有两个函数: + +- `IsDone() -> boolean` 会告知您所有处理程序是否已完成。还可以通过检查它的`MP.Sleep`-来等待变为True +- `GetResults() -> table` 返回一个未注释且未命名的表,其中包含所有处理程序的所有返回值,这是一个数组 + + 一定要用`Obj:Function()` 语法 (`:`, 不要 `.`). + +范例: + +```lua +local Future = MP.TriggerGlobalEvent("MyEvent") +-- wait until handlers finished +while not Future:IsDone() do + MP.Sleep(100) -- sleep 100 ms +end +local Results = Future:GetResults() +print(Results) +``` + +请注意,在这里注册到“MyEvent”的处理程序如果一直不返回,可能导致您的插件卡死,您可能需要跟踪等待的时间,并在等待几秒后停止等待。 + +#### `MP.Sleep(time_ms: number)` + +等待的时间以毫秒为单位指定 + +这不会让lua状态的执行,并且在休眠状态下不会执行任何操作。 + +警告:如果您注册了事件处理程序,请不要在>500毫秒内休眠,除非您确切地知道*您正在做什么。这是用来睡眠1-100毫秒,以便等待结果或类似的。如果不小心,锁定(睡眠)的lua状态可能会大大降低整个服务器的速度。* + +#### `MP.SendChatMessage(player_id: number, message: string)` + +发送一个聊天消息,只有指定的玩家可以看到(或所有人,如果ID是`-1`)。在游戏中,这不会显示为直接消息。 + +例如,你可以用它来告诉玩家为什么你取消了他们的车辆刷出,聊天信息,或者类似的,或者显示一些关于你的服务器的信息。 + +范例: + +```lua +function ChatHandler(player_id, player_name, msg) + if string.match(msg, "darn") then + MP.SendChatMessage(player_id, "Please do not use profanity.") -- If the player sends a message containing "darn", notify the player and cancel the message + return 1 + else + return 0 + end +end + +MP.RegisterEvent("onChatMessage", "ChatHandler") +``` + +范例 2: + +```lua +function ChatHandler(player_id, player_name, msg) + if msg == "hello" then + MP.SendChatMessage(-1, "Hello World!") -- If the player sends the exact message "hello", announce to the entire server "Hello World!" + return 0 + end +end +``` + +#### `MP.TriggerClientEvent(player_id: number, event_name: string, data: string) -> boolean` + +*在 v3.1.0 中* + +#### `MP.TriggerClientEvent(player_id: number, event_name: string, data: string) -> boolean,string` + +*在 v3.1.0 中* + +#### `MP.TriggerClientEventJson(player_id: number, event_name: string, data: table) -> boolean,string` + +*从 v3.1.0 以来* + +将使用指定客户机上的给定数据调用给定事件(-1表示广播)。这个事件可以在客户端lua mod中处理,请参阅“客户端脚本”文档。 + +如果能够发送消息,将返回`true`(对于`id = -1`,因为广播,它总是`true`),如果具有该id的播放器不存在或断开连接但仍有id(这是一个已知问题),则返回`false`。 + +如果返回`false`,则重试此事件没有意义,并且不应该期望响应(如果期望有响应)。 + +从v3.1.0开始,如果函数失败,第二个返回值包含一条错误消息。同样从这个版本开始,函数的`*Json`版本接受一个表作为数据参数,并将其转换为Json。这是` mp . triggerclienttevent(…)的简写。Util.JsonEncode (mytable)) {/ code1}。` + +#### `MP.GetPlayerCount() -> number` + +返回服务器中当前玩家的数量。 + +#### `MP.GetPositionRaw(pid: number, vid: number) -> table,string` + +返回玩家`pid`(玩家id)的车辆`vid`(车辆id)的当前位置,如果发生错误则返回错误字符串。 + +这个表是从位置数据包解码的,所以它有各种各样的数据,包括位置和旋转(这就是为什么这个函数后面加了“Raw”)。 + +范例: + +```lua +local player_id = 4 +local vehicle_id = 0 + +local raw_pos, error = MP.GetPositionRaw(player_id, vehicle_id) + +if error == "" then + print(raw_pos) +else + print(error) +end +``` + +输出: + +```json + { + tim: 49.824, // Time since spawn + rvel: { // Rotational velocity + 1: -1.33564e-05, + 2: -9.16553e-06, + 3: 8.33364e-07, + }, + vel: { // Velocity + 1: -4.29755e-06, + 2: -5.79335e-06, + 3: 4.95236e-06, + }, + pos: { // Position + 1: 269.979, + 2: -759.068, + 3: 46.554, + }, + ping: 0.0125, // Vehicle latency + rot: { // Rotation + 1: -0.00559953, + 2: 0.00894832, + 3: 0.772266, + 4: 0.635212, + }, +} +``` + +范例 2: + +```lua +local player_id = 4 +local vehicle_id = 0 + +local raw_pos, error = MP.GetPositionRaw(player_id, vehicle_id) +if error = "" then + local x, y, z = table.unpack(raw_pos["pos"]) + + print("X:", x) + print("Y:", y) + print("Z:", z) +else + print(error) +end +``` + +输出: + +``` +X: -603.459 +Y: -175.078 +Z: 26.9505 +``` + +#### `MP.IsPlayerConnected(player_id: number) -> boolean` + +玩家是否已连接以及服务器是否已收到来自其的UDP数据包。 + +范例: + +```lua +local player_id = 8 +print(MP.IsPlayerConnected(player_id)) -- Check if player with ID 8 is properly connected. +``` + +输出: + +```lua +true +``` + +#### `MP.GetPlayerName(player_id: number) -> string` + +获取玩家的显示名称。 + +范例: + +```lua +local player_id = 4 +print(MP.GetPlayerName(player_id)) -- Get the name of the player with ID 4 +``` + +输出: + +``` +ilovebeammp2004 +``` + +#### `MP.RemoveVehicle(player_id: number, vehicle_id: number)` + +移除指定玩家的指定车辆。 + +范例: + +```lua +local player_id = 3 +local player_vehicles = MP.GetPlayerVehicles(player_id) + +-- Loop over all of player 3's vehicles and delete them +for vehicle_id, vehicle_data in pairs(player_vehicles) do + MP.RemoveVehicle(player_id, vehicle_id) +end +``` + +#### `MP.GetPlayerVehicles(player_id: number) -> table` + +返回玩家当前拥有的所有车辆的表。表中的每个条目都是从车辆ID到车辆数据(目前是一个原始json字符串)的映射。 + +范例: + +```lua +local player_id = 3 +local player_vehicles = MP.GetPlayerVehicles(player_id) + +for vehicle_id, vehicle_data in pairs(player_vehicles) do + local start = string.find(vehicle_data, "{") + local formattedVehicleData = string.sub(vehicle_data, start, -1) + print(Util.JsonDecode(formattedVehicleData)) +end +``` + +输出: + +```json +{ + pid: 0, + pro: "0", + rot: { + 1: 0, + 2: 0, + 3: 0.776866, + 4: 0.629665, + }, + jbm: "miramar", + vcf: { + parts: { + miramar_exhaust: "miramar_exhaust", + miramar_shock_R: "miramar_shock_R", + miramar_taillight: "miramar_taillight", + miramar_door_RL: "miramar_door_RL" + // ... continue + }, + paints: { + 1: { + roughness: 1, + metallic: 0, + clearcoat: 1, + baseColor: { + 1: 0.85, + 2: 0.84, + 3: 0.8, + 4: 1.2, + }, + clearcoatRoughness: 0.09, + } // ... continue + }, + partConfigFilename: "vehicles/miramar/base_M.pc", + vars: {}, + mainPartName: "miramar", + }, + pos: { + 1: 283.669, + 2: -754.332, + 3: 48.2151, + }, + vid: 64822, + ign: 0, +} +``` + +#### `MP.GetPlayers() -> table` + +返回所有已连接玩家的表。这个表将id映射到name,如下所示: + +```json +{ + 0: "LionKor", + 1: "JohnDoe" +} +``` + +#### `MP.IsPlayerGuest(player_id: number) -> boolean` + +玩家是否为游客。游客指未进行登录,而是直接选择以游客身份游玩的用户,其名称通常为`guest`后接一长串数字。 + +由于游客是匿名的,您可能希望禁止他们加入,如果是这样,建议使用[`onPlayerAuth`](#onplayerauth) `is_guest`参数。 + +#### `MP.DropPlayer(player_id: number, [reason: string])` + +踢出带有指定ID的玩家。reason为可选参数。 + +```lua +function ChatHandler(player_id, player_name, message) + if string.match(message, "darn") then + MP.DropPlayer(player_id, "Profanity is not allowed") + return 1 + else + return 0 + end +end +``` + +#### `MP.GetStateMemoryUsage() -> number` + +以字节为单位返回当前Lua状态的内存使用情况。 + +#### `MP.GetLuaMemoryUsage() -> number` + +返回所有lua状态的内存使用情况,以字节为单位。 + +#### `MP.GetPlayerIdentifiers(player_id: number) -> table` + +返回一个包含玩家信息的表,例如 BeamMP 论坛 ID、IP 地址及 Discord 账户 ID。Discord ID 仅会在用户已将其绑定至论坛账户时返回。 + +您可通过访问`https://forum.beammp.com/u/USERNAME.json`并在返回数据中查找`"user": {"id": 123456}`以获取用户的论坛 ID。BeamMP ID 是玩家的唯一标识,与用户名不同,该 ID 一经设定便不可更改。 + +范例: + +```lua +local player_id = 5 +print(MP.GetPlayerIdentifiers(player_id)) +``` + +输出: + +```json +{ + ip: "127.0.0.1", + discord: "12345678987654321", + beammp: "1234567", +} +``` + +*在 v3.1.0 之前的版本中,`ip` 字段存在错误且无法正常工作,该问题已在 v3.1.0 版本中修复。* + +#### `MP.Set(setting: number, ...)` + +临时设置服务器配置项。为此,`MP.Settings` 表格非常实用。 + +范例: + +```lua +MP.Set(MP.Settings.Debug, true) -- Turns on debug mode +``` + +#### `MP.Settings -> table` + +设置项ID与名称的对照表。配合`MP.Set`使用,用于更改服务器配置项。 + +范例: + +```lua +print(MP.Settings) +``` + +输出: + +```json +{ + MaxPlayers: 3, + Debug: 0, + Name: 5, + Description: 6, + MaxCars: 2, + Private: 1, + Map: 4, +} +``` + +### Util 功能 + +#### `Util.Json*` + +从 BeamMP-Server `v3.1.0`以来。 + +这是内置JSON库,其性能通常远超任何Lua JSON库。底层使用C++的`nlohmann::json`库实现,严格遵循JSON规范,具备完整单元测试覆盖且持续进行模糊测试。 + +#### `Util.JsonEncode(table: table) -> string` + +将Lua表递归编码为JSON字符串(支持表中有表、表中再有表……等多层嵌套结构),所有基本类型均会被保留,函数、userdata及类似数据则会被忽略。 + +生成的JSON为压缩格式,可使用`Util.JsonPrettify`对其进行美化排版。 + +范例: + +```lua +local player = { + name = "Lion", + age = 69, + skills = { "skill A", "skill B" } +} +local json = Util.JsonEncode(player) +``` + +结果: + +```json +{"name":"Lion","age":69,"skills":["skill A","skill B"]} +``` + +#### `Util.JsonDecode(json: string) -> table` + +将JSON解码为Lua表。如果失败,将返回`nil`,并输出错误。 + +范例: + +```lua +local json = "{\"message\":\"OK\",\"code\":200}" +local tbl = Util.JsonDecode(json) +``` + +结果: + +```lua +{ + message = "OK", + code = 200, +} +``` + +#### `Util.JsonPrettify(json: string) -> string` + +向json中添加缩进和换行,使其更易于人们阅读。 + +实例: + +``` +local myjson = Util.JsonEncode({ name="Lion", age = 69, skills = { "skill A", "skill B" } }) + +print(Util.JsonPrettify(myjson)) +``` + +结果: + +```json +{ + "age": 69.0, + "name": "Lion", + "skills": [ + "skill A", + "skill B" + ] +} +``` + +#### `Util.JsonMinify(json: string) -> string` + +Removes indentation, newlines and any other whitespace. Not necessary unless you called `Util.JsonPrettify`, as all output from `Util.Json*` is already minified. + +范例: + +```lua +local pretty = Util.JsonPrettify(Util.JsonEncode({ name="Lion", age = 69, skills = { "skill A", "skill B" } })) + +print(Util.JsonMinify(pretty)) +``` + +结果: + +```json +{"age":69.0,"name":"Lion","skills":["skill A","skill B"]} +``` + +#### `Util.JsonFlatten(json: string) -> string` + +创建符合RFC 6901标准的JSON扁平化对象(将键名转换为JSON指针路径)。您可通过`Util.JsonUnflatten()`还原原始结构,但要求所有键值必须为基本数据类型。 + +范例: + +```lua +local json = Util.JsonEncode({ name="Lion", age = 69, skills = { "skill A", "skill B" } }) +print("normal: " ..json) +print("flattened: " .. Util.JsonFlatten(json)) +print("flattened pretty: " .. Util.JsonPrettify(Util.JsonFlatten(json))) + +``` + +结果: + +```json +normal: {"age":69.0,"name":"Lion","skills":["skill A","skill B"]} +flattened: {"/age":69.0,"/name":"Lion","/skills/0":"skill A","/skills/1":"skill B"} +flattened pretty: { + "/age": 69.0, + "/name": "Lion", + "/skills/0": "skill A", + "/skills/1": "skill B" +} +``` + +#### `Util.JsonUnflatten(json: string) -> string` + +还原由`Util.JsonFlatten()`函数扁平化处理的JSON值的任意嵌套结构。 + +#### `Util.JsonDiff(a: string, b: string) -> string` + +根据RFC 6902规范(http://jsonpatch.com/)生成JSON差异文件。可通过`Util.JsonDiffApply()`方法应用该差异补丁,最终返回差异结果集 + +#### `Util.JsonDiffApply(base: string, diff: string) -> string` + +将JSON `diff`作为补丁应用于`base`(遵循RFC 6902标准,详见http://jsonpatch.com/),最终返回处理结果。 + +### `Util.Random*` + +从 BeamMP-Server `v3.1.0`开始。 + +#### `Util.Random() -> float` + +返回一个介于0到1之间的浮点数值。 + +范例: + +```lua +local rand = Util.Random() +print("rand: " .. rand) +``` + +结果: + +```lua +rand: 0.135477 +``` + +#### `Util.RandomIntRange(min: int, max: int) -> int` + +返回一个介于min和max之间的整数。 + +范例: + +```lua +local randInt = Util.RandomIntRange(1, 100) +print("randInt: " .. randInt) +``` + +结果: + +```lua +randInt: 69 +``` + +#### `Util.RandomRange(min: number, max: number) -> float` + +返回一个介于min和max之间的浮点数。 + +范例: + +```lua +local randFloat = Util.RandomRange(1, 1000) +print("randFloat: " .. randFloat) +``` + +结果: + +```lua +randFloat: 420.6969 +``` + +#### `Util.LogInfo(params: ...)` 及其关联方法集(自 v3.3.0 版本起) + +```lua +Util.LogInfo("Hello, World!") +Util.LogWarn("Cool warning") +Util.LogError("Oh no!") +Util.LogDebug("hi") +``` + +输出的内容 + +``` +[19/04/24 11:06:50.142] [Test] [INFO] Hello, World! +[19/04/24 11:06:50.142] [Test] [WARN] Cool warning +[19/04/24 11:06:50.142] [Test] [ERROR] Oh no! +[19/04/24 11:06:50.142] [Test] [DEBUG] hi +``` + +支持与`print()`方法完全一致的数据打印/转储能力。 + +#### `Util.DebugExecutionTime() -> table` + +当Lua代码在服务器端运行时,系统会对每个事件处理器的执行进行计时。这些执行时间的最小值、最大值、平均值(算术平均数)和标准偏差会被计算,并通过本函数以表格形式返回。该计算采用增量方式进行,因此每当事件处理器运行时,最小值、最大值、平均值和标准偏差都会实时更新。这样设计确保`Util.DebugExecutionTime()`方法的执行时间通常极短(低于0.25毫秒)。 + +它会返回一个这样的表: + +```lua +[[table: 0x7af6d400aca0]]: { + printStuff: [[table: 0x7af6d400be60]]: { + mean: 0.250433, + n: 76, + max: 0.074475, + stdev: 0.109405, + min: 0.449274, + }, + onInit: [[table: 0x7af6d400b130]]: { + mean: 0.033095, + n: 1, + max: 0.033095, + stdev: 0, + min: 0.033095, + }, +} +``` + +对于每个事件*处理器*,将返回如下结构数据: + +- `n`: 触发事件和调用处理程序的次数 +- `mean`: 所有执行时间的平均值/中间值,单位为ms +- `max`: 最长执行时间,单位为ms +- `min`: 最短的执行时间,单位为毫秒 +- `stdev`: 所有执行时间平均值的标准偏差,单位为ms + +这里有一个函数,你可以用它来漂亮地输出这些数据: + +```lua +function printDebugExecutionTime() + local stats = Util.DebugExecutionTime() + local pretty = "DebugExecutionTime:\n" + local longest = 0 + for name, t in pairs(stats) do + if #name > longest then + longest = #name + end + end + for name, t in pairs(stats) do + pretty = pretty .. string.format("%" .. longest + 1 .. "s: %12f +/- %12f (min: %12f, max: %12f) (called %d time(s))\n", name, t.mean, t.stdev, t.min, t.max, t.n) + end + print(pretty) +end +``` + +如果它很慢,你可以像这样调用它来调试你的代码: + +```lua +-- event to print the debug times +MP.RegisterEvent("printStuff", "printDebugExecutionTime") +-- run every 5000 ms = 5 seconds (or 10, or 60, whatever makes sense for you +MP.CreateEventTimer("printStuff", 5000) +``` + +### FS的功能 + +`FS`函数是**f**ile**s** system函数,目的是比默认的Lua功能更好。 + +在指定路径时,请始终使用`/`作为分隔符,因为这是跨平台的(windows, linux, macos,…)。 + +#### `FS.CreateDirectory(path: string) -> bool,string` + +创建指定的目录和任何父目录(如果父目录不存在)。其行为大致相当于常见的linux命令`mkdir -p`。 + +如果成功,返回`true`和`""`。如果创建目录失败,则返回`false`和错误消息(`string`)。 + +范例: + +```lua +local success, error_message = FS.CreateDirectory("data/mystuff/somefolder") + +if not success then + print("failed to create directory: " .. error_message) +else + -- do something with the directory +end + +-- Be careful not to do this! This will ALWAYS be true! +if error_message then + -- ... +end +``` + +#### `FS.Remove(path: string) -> bool,string` + +删除指定的文件或文件夹。 + +如果发生错误,返回`true`,并在第二个返回值中显示错误消息。 + +范例: + +```lua +local error, error_message = FS.Remove("myfile.txt") + +if error then + print("failed to delete myfile: " .. error_message) +end +``` + +#### `FS.Rename(pathA: string, pathB: string) -> bool,string` + +将`pathA`重命名(或移动)为`pathB`。 + +如果发生错误,返回`true`,并在第二个返回值中显示错误消息。 + +#### `FS.Copy(pathA: string, pathB: string) -> bool,string` + +复制 `pathA` 到 `pathB`. + +如果发生错误,返回`true`,并在第二个返回值中显示错误消息。 + +#### `FS.GetFilename(path: string) -> string` + +返回路径的最后一部分,通常是文件名。下面是一些输入+输出示例: + +```lua +input -> output + +"my/path/a.txt" -> "a.txt" +"somefile.txt" -> "somefile.txt" +"/awesome/path" -> "path" +``` + +#### `FS.GetExtension(path: string) -> string` + +返回文件的扩展名,如果不存在扩展名则返回空字符串。下面是一些输入+输出示例 + +```lua +input -> output + +"myfile.txt" -> ".txt" +"somefile." -> "." +"/awesome/path" -> "" +"/awesome/path/file.zip.txt" -> ".txt" +"myexe.exe" -> ".exe" +``` + +#### `FS.GetParentFolder(path: string) -> string` + +返回父目录的路径,即包含文件或文件夹的文件夹。下面是一些输入+输出示例: + +```lua +input -> output + +"/var/tmp/example.txt" -> "/var/tmp" +"/" -> "/" +"mydir/a/b/c.txt" -> "mydir/a/b" +``` + +#### `FS.Exists(path: string) -> bool` + +如果路径存在返回`true`,如果路径不存在返回`false`。 + +#### `FS.IsDirectory(path: string) -> bool` + +如果指定的路径是目录,返回`true`,如果不是,返回`false`。注意`false`并不意味着路径是一个文件(参见`FS.IsFile()`)。 + +#### `FS.IsFile(path: string) -> bool` + +如果指定的路径是目录,返回`true`,如果不是,返回`false`。注意`false`并不意味着路径是一个文件(参见`FS.IsFile()`)。 + +#### `FS.ListDirectories(path: string) -> table` + +返回给定路径中所有目录的表。 + +范例: + +```lua +print(FS.ListDirectories("Resources")) +``` + +结果: + +```lua +{ + 1: "Client", + 2: "Server" +} +``` + +#### `FS.ListFiles(path: string) -> table` + +返回给定路径中所有目录的表。 + +范例: + +```lua +print(FS.ListFiles("Resources/Server/examplePlugin")) +``` + +结果: + +```lua +{ + 1: "example.json", + 2: "example.lua" +} +``` + +#### `FS.ConcatPaths(...) -> string` + +使用系统的首选路径分隔符将所有参数加在一起(连接)。 + +范例: + +```lua +FS.ConcatPaths("a", "b", "/c/d/e/", "/f/", "g", "h.txt") +``` + +结果 + +``` +a/b/c/d/e/f/g/h.txt +``` + +当路径中存在`..`符号时,本函数会执行智能路径解析。该方法相较于Lua原生字符串拼接更为安全,且自动适配不同操作系统的路径分隔符。 + +在指定路径时,请始终使用 `/`作为分隔符,因为它是跨平台通用的(Windows,Linux,MacOS等) + +### 事件 + +#### 说明 + +- 参数:给出此事件处理程序的参数列表 +- 可取消:表示该事件是否可以被取消。如果事件可取消,处理函数可以通过返回 `1`, 来取消事件,例如 `return 1`. + +#### 事件摘要 + +玩家的加入将按照给定的顺序触发以下事件: + +1. `onPlayerAuth` +2. `onPlayerConnecting` +3. `onPlayerJoining` +4. `onPlayerJoin` + +#### 系统事件 + +##### `onInit` + +参数: NONE 可取消: NO + +在插件中的所有文件初始化后触发。 + +##### `onConsoleInput` + +参数: `input: string` 可取消: NO + +当BeamMP控制台接收到输入时触发。 + +##### `onShutdown` + +参数: NONE 可取消: NO + +服务器关闭时触发。目前发生在所有玩家被踢之后。 + +#### 游戏相关事件 + +##### `onPlayerAuth` + +参数: `player_name: string`, `player_role: string`, `is_guest: bool`, `identifiers: table -> beammp, ip` 可取消: YES + +当玩家尝试加入时触发的第一个事件。
处理函数可以通过返回`1` 或一个原因 (`string`) 来拒绝玩家加入。 + +```lua +function myPlayerAuthorizer(name, role, is_guest, identifiers) + return "Sorry, you cannot join at this time." +end +MP.RegisterEvent("onPlayerAuth", "myPlayerAuthorizer") +``` + +##### `onPlayerConnecting` + +参数: `player_id: number` 可取消: NO + +当玩家第一次开始连接时触发`onPlayerAuth`。 + +##### `onPlayerJoining` + +参数: `player_id: number` 可取消: NO + +当玩家加载完所有mod后触发`onPlayerConnecting`。 + +##### `onPlayerDisconnect` + +参数: `player_id: number` 可取消: NO + +当玩家断开连接时触发。 + +##### `onChatMessage` + +参数: `player_id: number`, `player_name: string`, `message: string` 可取消: YES + +当玩家发送聊天消息时触发。
如果该事件被取消,聊天消息将不会对任何人显示,甚至发送消息的玩家自己也看不到。 + +##### `onVehicleSpawn` + +参数: `player_id: number`, `vehicle_id: number`, `data: string` 可取消: YES + +当玩家生成一辆新车辆时触发。参数 `data`包含该车辆的配置,以及位置和旋转等信息,格式为 JSON 字符串。 + +##### `onVehicleEdited` + +参数: `player_id: number`, `vehicle_id: number`, `data: string` 可取消: YES + +当玩家编辑并应用其车辆修改时触发。参数 `data` 包含车辆更新后的配置,格式为 JSON 字符串, 但 **不**包含位置或旋转数据。你可以使用 [MP.GetPositionRaw](#mpgetpositionrawpid-number-vid-number-tablestring) 来获取位置和旋转数据。 + +##### `onVehicleDeleted` + +参数: `player_id: number`, `vehicle_id: number` 可取消: NO + +当玩家删除其车辆时触发。 + +##### `onVehicleReset` + +参数: `player_id: number`, `vehicle_id: number`, `data: string` 可取消: NO + +当玩家重置其车辆时触发。参数 `data`包含车辆更新后的位置和旋转信息,但 **不** 包含车辆的配置,你可以使用 [MP.GetPlayerVehicles](#mpgetplayervehiclesplayer_id-number-table) 来获取车辆配置。 + +##### `onFileChanged` + +*在 v3.1.0* + +参数: `path: string` 可取消: NO + +当 `Resources/Server` 目录或其任意子目录中的文件发生变化时触发。 + +当 `Resources/Server/` 目录中的任意文件(不包括其子文件夹)发生变化时,将触发一次 Lua 状态重载,并触发一个 `onFileChanged` 事件。 + +位于 `Resources/Server/` 子文件夹中的任何文件(例如 `Resources/Server//lua/stuff.lua`)发生变化时,不会触发 Lua 状态重载,而只会触发一个 `onFileChanged` 事件。
这样你可以自行选择是否以及如何以正确的方式重新加载它。 + +这适用于所有文件,不仅仅是`.lua` 文件。 + +参数 `path` 是相对于服务器根目录的路径,例如`Resources/Server/myplugin/myfile.txt`。你可以使用`FS.*`系列函数对该字符串进行进一步处理,例如提取文件名或扩展名(如(`FS.GetExtension(...)`, `FS.GetFilename(...)`, ...)。 + +注意: 自 v3.1.0 起,服务器启动后新增的文件将*不会*被追踪。 + +### 从旧Lua迁移 + +本文简要介绍了从旧lua迁移到新lua的基本步骤。 + +#### 理解新的lua如何工作 + +为此,请仔细阅读[“介绍”](#how-to-start-writing-a-plugin)部分及其所有子部分。这是正确进行后续步骤所必需的。 + +#### 搜索和替换 + +首先,你需要搜索并替换所有 MP 函数。替换时应在所有 MP 函数前加上 `MP.` 前缀,但`print()`函数除外。 + +范例: + +```lua +local players = GetPlayers() +print(#players) +``` + +替换为 + +```lua +local players = MP.GetPlayers() +print(#players) -- note how print() doesn't change +``` + +#### 再见线程,你好事件计时器! + +如在 “介绍” 部分所述,线程(threads)即事件定时器(event timers)。
对于所有调用 `CreateThread` 的地方,应将其替换为 `CreateEventTimer`。

请仔细检查你原先的 `CreateThread` 的执行频率(每秒运行 X 次),并据此计算出事件定时器的超时时间(以毫秒为单位)。

另外请注意,`CreateEventTimer` 传入的不是函数名,而是事件名,因此你还需要注册一个事件来配合使用。 + +范例: + +```lua +CreateThread("myFunction", 2) -- calls "myFunction" twice per second +``` + +替换为 + +```lua +MP.RegisterEvent("myEvent", "myFunction") -- registering our event for the timer +MP.CreateEventTimer("myEvent", 500) -- 500 milliseconds = 2 times per second +``` + +如果你有很多事件定时器,那么可以考虑将它们合并。
例如,你可以创建一个 “每分钟” 事件,然后将所有需要每分钟调用的函数都注册到这个事件上,而不是为每个函数单独创建一个事件定时器。

因为每个事件定时器在触发时都会让服务器多消耗一点时间。 + +#### 不再支持隐式事件调用 + +你需要注册所有的事件,不能再依赖函数名。
在旧版 Lua 中,这一点并不明确,但在新版 Lua 中通常会强制要求这样做。
一个好的示例模式是: + +```lua +MP.RegisterEvent("onChatMessage", "chatMessageHandler") +-- or +MP.RegisterEvent("onChatMessage", "handleChatMessage") +``` + +这种做法比让事件处理函数与事件同名要更好,因为后者容易造成误导和混淆。 + + diff --git a/docs/zh/server/port-forwarding.md b/docs/zh/server/port-forwarding.md new file mode 100644 index 0000000..a0d2750 --- /dev/null +++ b/docs/zh/server/port-forwarding.md @@ -0,0 +1,220 @@ +# 端口转发 + +!!! 风险 ":material-scale-balance: 免责声明:" + +``` +“端口转发存在风险”。 + +通过端口转发,你理解将家庭网络端口向公众开放的风险,因此无权追究 BeamMP 对你或你家庭可能发生的 任何 损害负责。 + +我们对任何外部链接服务或网站上的内容不承担任何责任。 + +如果你不理解本指南,请考虑使用我们的合作伙伴提供的服务。 +``` + +!!! 警告 + +``` +请确保你的路由器不是仅支持 4G/5G 的设备。如果是混合设备,请在本指南第 3 节中选择有线连接的适配器! +``` + +## 怎么设置端口转发 + +创建端口转发规则涉及一些详细的网络术语。在操作过程中,请准备记录一些笔记。 + +本指南共有 4 个主要步骤。 + +## 快速指南(更详细的指南见下文) + +
+
+
    +
  • +

    :material-dns:{ .lg .middle } 为你的电脑或设备分配一个静态 IP 地址

    +
    +

    这是为了防止你的设备 IP 发生变化,从而导致端口转发规则失效。

    +

    :octicons-arrow-right-24: 查看你的路由器信息

    +
  • +
  • +

    :material-router-wireless:{ .lg .middle } 登录你的路由器

    +
    +

    通常可以通过查找‘默认网关’IP 来完成,此 IP 可在命令提示符执行 ipconfig 时找到,然后将其输入浏览器地址栏。

    +
  • +
  • +

    :material-lan-connect:{ .lg .middle } 将端口转发到你的电脑

    +
    +

    在路由器的网页界面中找到端口转发部分。大多数路由器会将端口转发部分列在网络(Network)、高级(Advanced)或局域网(LAN)下。

    +
  • +
  • +

    :material-test-tube:{ .lg .middle } 测试端口是否已正确转发

    +
    +

    使用像 CheckBeamMP 这样的工具测试规则是否生效。

    +
    +
    + +
    + +
    + +
    +
    +
  • +
+
+ +## 详细指南 + +### 1. 分配静态 IP 地址 + +### 方法 1:通过 DHCP 保留设置静态 IP 地址 + +在本地网络中设置静态 IP 地址的另一种方法是使用路由器的 DHCP 保留功能。并非所有路由器都具备此功能,因此这可能不适用于你。请使用你的路由器型号在网上搜索使用手册。 + +如果你已经完成此操作,请直接跳到 [第 2 步](port-forwarding.md#2-log-in-to-your-router)。 + +### 方法 2:在 Windows 中分配静态 IP 地址 + +#### 1.1. 查找您当前的IP地址、网关和DNS服务器: + +在设置静态IP地址之前,我们需要先了解您当前的网络设置。您可能需要记下这些信息,因此请提前打开记事本窗口备用。这一步我们将使用命令提示符进行操作。 + +打开命令提示符。主要有以下三种方式: + +- 按下Windows键,然后输入“cmd”,当看到“命令提示符”高亮显示时按Enter键。 + +
![](../../assets/content/win11-open-cmd.png)
+ +一旦您进入了命令提示符,运行以下命令: + +``` +ipconfig /all +``` + +你会看到大量数据。如果你有虚拟或多个网络适配器,那么你会看到更多数据。如果安装了 Hyper-V 或 Docker,通常会出现许多虚拟适配器。 + +
![](../../assets/content/win11-command-prompt-ipconfig-highlighted.png)
+ +建议使用有线网络连接来运行此服务器,但无线连接也可正常工作。您需要在此列表中寻找具有活跃互联网连接的适配器。滚动列表并找到已分配默认网关的适配器,许多虚拟适配器通常没有默认网关。 + +以下是本地IPv4地址示例,至少有一个适配器应具备这些地址。您需要记下适配器的信息。 + +- 192.168.x.x +- 10.x.x.x. +- 172.16.x.x - 172.31.x.x + +子网掩码(常见取值为255.255.255.0)
默认路由地址(典型配置为192.168.0.1或192.168.1.1) + +!!! info "请注意" BeamMP 目前不支持使用 IPv6 协议托管服务器。 + +#### 1.2. 修改适配器设置 + +现在我们需要更改网络适配器的设置,以便您的计算机能够维持当前分配的IP配置。访问网络设置的最快捷途径是: + +- 单击Windows键 +- 输入“网络连接”,直到您看到“查看网络连接”。 +- 按下回车键 + +
![](../../assets/content/win11-start-menu-view-network-connections.png)
+ +你会看到电脑上的网络连接列表。如果你安装了 Hyper-V 或 Docker,可能会有很多适配器。请寻找任何不命名为 "Hyper-V" 的适配器。 + +
![](../../assets/content/win11-network-connections.png)
+ +右键点击您的适配器并选择属性。如果`互联网协议版本4`未被勾选,则表明此适配器选择有误,请更换其他适配器。 + +
![](../../assets/content/win11-ethernet-properties-highlighted.png)
+ +双击`互联网协议版本4`。将`自动获取IP地址`更改为`使用以下IP地址`。 + +使用命令提示符(ipconfig /all)中的信息填写IP地址、子网掩码、默认网关和首选DNS服务器。 + +或者,您也可以不使用当前DNS服务器,而选择CloudFlare或Google提供的DNS服务器: + +- CloudFlare DNS: 1.1.1.1, 1.0.0.1 +- Google DNS: 8.8.8.8, 8.8.4.4 + +
![](../../assets/content/win11-network-settings-static-ip.png)
+ +点击确定,然后再次点击确定,您的适配器现在已从DHCP更改为静态IP配置。请上网浏览以确认是否仍然保持互联网连接。如果无法连接,请将设置改回自动获取IP地址,并尝试下一种方法。 + +### 2. 登录您的路由器 + +既然您的设备已设置静态IP地址,现在可以开始为BeamMP配置端口转发。 + +首先,我们需要登录到您的路由器。之前您记录下来的设置中包括默认网关,该地址即为您路由器的IP地址。 + +大多数路由器通过本地网页界面进行管理。要访问路由器的管理菜单和设置: + +- 打开一个浏览器,Firefox, Chrome 或 Edge应该都可以正常工作 +- 在地址栏中,输入您的默认网关IP地址,例如 192.168.0.1 或 192.168.1.1,然后按回车键。 + +现在您应该能看到路由器的登录界面。并非所有路由器都需要登录,但大多数都需要。您需要知道路由器的用户名和密码。如果是首次登录,用户名和密码很可能仍为出厂默认值,或者在某些情况下会印在路由器的贴纸标签上。 + +以下是部分最常见的出厂默认用户名与密码列表: + +用户名 | 密码 +--- | --- +admin | admin +admin | password +{blank} | admin +{blank} | password + +尝试使用admin、password等不同组合,或直接留空输入框。*若提示留空,请尝试不填写任何内容。* + +### 3. 创建转发规则 + +#### 3.1. 查找端口转发设置的地方 + +在路由器网页界面中找到端口转发设置区域。请通过点击页面顶部或左侧的选项卡或链接来浏览路由器菜单。大多数路由器会将端口转发功能列在"网络"、"高级"或"局域网"等分类下。留意查找以下关键词来定位该功能: + +- 端口转发 +- 转发 +- 端口范围转发 +- 虚拟服务器 +- 应用程序 & 游戏 +- 高级配置/设置 +- NAT + +#### 3.2. 填写详细信息 + +找到路由器的端口转发设置后,即可开始输入必要信息。路由器会提供输入区域用于填写需要转发的端口号以及对应的目标IP地址。若路由器同时显示内部端口和外部端口选项,请确保填写相同的端口号以保持一致。 + +BeamMP 需要同时开放 UDP 和 TCP 协议的 30814 端口(除非您已在 [ServerConfig.toml](create-a-server.md#4-configuration) 配置文件中修改过此端口)。 + +!!! info "注意"
虽然默认端口为 **30814**,您也可以选择1024至65535之间的其他端口号(若使用非默认端口需自行记录)。需要同时转发 **TCP** 和 **UDP** 协议。
建议保持使用默认端口,因此端口被本机其他服务占用的可能性极低。若需在同一台机器上部署多个服务器,则每个服务器需使用不同端口(例如:服务器1使用30814,服务器2使用30815)。 + +在某些路由器中,您可能需要创建两条规则(一条用于UDP,另一条用于TCP),而有些路由器则更便捷,允许您通过单条规则同时设置两种协议! + +绝大多数路由器都设有“保存”按钮,而部分机型在更改生效前还需重启或复位系统。 + +### 4. 测试一下! + +连接测试可通过以下几种不同方式进行。 + +我们推荐的方式是使用我们的工具 **CheckBeamMP**,因为它能够检测BeamMP特有的问题和协议。 + +
+ +
+ +
+ +
+ +可通过获取您的公网IPv4地址来实现,该方法同样有几种不同的操作方式。主要方法是访问名为 [whatsmyip.org](https://whatsmyip.org/) 的网站,这是一个能直接显示您公网IP地址的简易网站。您需要找到格式为 xxx.xxx.xxx.xxx 的IP地址。 + +访问以下链接,并将“IP”替换为您的实际IPv4地址,“Port”替换为您的服务器端口。请确保不留空格。https://check.beammp.com/api/v2/beammp/ip/port + +!!! 成功 "status: ok" + +``` +若获得上述输出结果,即可加入您的服务器! 有两种连接方式:既可通过在端口转发工具中配置的详细信息直接连接,也可在服务器设置为"公开"状态下通过服务器列表加入。 由于您采用的是本地托管服务器,若服务器与游戏运行在同一台电脑上,请使用 127.0.0.1(本地主机);若服务器运行在局域网内的其他机器上,则使用该服务器的局域网IPv4地址。 +``` + +!!! 失败 "status: error" + +``` + + +如果连接完全失败,您的ISP可能正在使用CGNAT(运营商级网络地址转换)。有关更多详细信息,请查看[如何检查CGNAT?](../FAQ/How-to-check-for-CGNAT.md),或者在我们的[Discord服务器](https://discord.gg/beammp)的`#support`频道中提交服务器支持工单,我们的工作人员会处理您的工单!如果您只看到TCP工作而UDP失败,请再次检查防火墙和端口转发规则。 +``` diff --git a/docs/zh/server/server-maintenance.md b/docs/zh/server/server-maintenance.md new file mode 100644 index 0000000..1fdedc6 --- /dev/null +++ b/docs/zh/server/server-maintenance.md @@ -0,0 +1,245 @@ +# 服务器维护 + +有关如何配置和维护 BeamMP 服务器的指南、提示和技巧。 + +## 怎么安装 + +有关安装说明,请查看 [服务器安装](create-a-server.md). + +## ServerConfig文件 + +服务器配置,即一个名为`ServerConfig.toml`的文件, 使用了 [TOML 格式](https://toml.io/en/). + +*注意*:*旧的*服务器配置文件名为`Server.cfg`,但该文件已不再使用,若其仍存在服务器将发出警告。另请注意,两种配置格式**不**相互兼容。 + +该配置文件默认包含一个名为`[General]`的节,该节包含以下值: + +Key | 值类型 | 描述 +--- | --- | --- +Port | 1024-65535 | 服务器可访问的网络端口。(必须是唯一的,且不能被同一主机上的其他服务占用) +AuthKey | 认证密钥格式为`xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`,其中所有x代表字母数字字符(数字和字母)。 | 用于通过后端识别公共集合服务器。 +AllowGuests | true/false | 决定游客是否可以加入服务器。 +LogChat | true/false | 当启用(设置为“true”)时,聊天消息会被记录在 server.log 文件中。 +Debug | true /false | 当启用(true)时,将在日志中显示更多消息并提供更多信息。如果遇到问题,请启用此选项。启用此选项将显著增加日志文件的大小。 +Private | true/false | 当启用(true)时,您的服务器将不会显示在服务器列表中。任何拥有正确IP地址和端口的人仍然可以连接。 +InformationPacket | true/false | 当启用(true)时,服务器将允许未经身份验证的客户端直接通过服务器获取与服务器列表中相同的信息。 +Name | Any "text" | 在服务器列表中显示为您的服务器名称/标题。您可以使用特殊字符来为其添加颜色和样式。 +Tags | 请参见下方允许的标签列表。 | 标签用于搜索 e.g. Police, Racing etc... +MaxCars | 数字永远 ≥ 1 | 每位玩家的最大车辆数量。玩家尝试生成的额外车辆将被立即删除。 +MaxPlayers | 数字永远 ≥ 1 | 每个服务器的最大玩家数量。这不影响车辆数量。 +Map | 需要一个有效的地图位置,例如`/levels/gridmap_v2/info.json` | 你的服务器将要托管的地图。必须默认安装(下方可找到列表)或作为服务器模组安装。 +Description | 永远只能填写 "文本" | 在服务器列表中显示为服务器的描述(如果服务器是公开的)。你可以使用特殊字符来格式化这个描述,包括颜色和样式。 +ResourceFolder | 有效的文件夹位置,例如 "D:\Server\BeamMP\Resources" | 便于将服务器和资源文件夹分开存储。 +ImScaredOfUpdates | true/false | 这设置服务器在新版本发布时是否自动更新。 +UpdateReminderTime | 任何带有s、min、h、d后缀的数字(例如30s)。 | 设置在终端中打印的更新提醒消息的间隔。 + +其他部分可以并且应该由服务器插件使用(Lua API 即将推出),例如:`[MyMod]`。 + +AuthKey **必须**由你自己来设置。
默认情况下它是空的,需要填入你之前安装步骤中获得的 AuthKey。
请勿将此密钥分享给任何人,在截图时要完全模糊处理此密钥。 + +### 所有原版地图名称 + +以下是所有原版地图: + +- /levels/gridmap_v2/info.json +- /levels/johnson_valley/info.json +- /levels/automation_test_track/info.json +- /levels/east_coast_usa/info.json +- /levels/hirochi_raceway/info.json +- /levels/italy/info.json +- /levels/jungle_rock_island/info.json +- /levels/industrial/info.json +- /levels/small_island/info.json +- /levels/smallgrid/info.json +- /levels/utah/info.json +- /levels/west_coast_usa/info.json +- /levels/driver_training/info.json +- /levels/derby/info.json + +### 自定义你的服务器名称外观 + +在服务器列表中,在你的文本前使用这些特殊符号,即可为该文本应用对应效果: + +值 | 描述 +:-: | --- +`^r` | 重设 +`^p` | 换行(仅描述) +`^n` | 下划线 +`^l` | 加粗 +`^m` | 删除线 +`^o` | 斜体字 +`^0` | 黑色 +`^1` | 蓝色 +`^2` | 绿色 +`^3` | 亮蓝色 +`^4` | 红色 +`^5` | 粉色 +`^6` | 橙色 +`^7` | 灰色 +`^8` | 深灰色 +`^9` | 亮紫色 +`^a` | 亮绿色 +`^b` | 亮蓝色 +`^c` | 深橙色 +`^d` | 亮粉色 +`^e` | 黄色 +`^f` | 白色 + +### 自定义您的服务器标签 + +标签可用于让人们搜索特定类型的服务器。
你的 serverConfig.toml 将会自动生成带有 freeroam 标签的配置:
`Tags = "Freeroam"` + +你可以添加多个标签,用逗号分隔
`Tags = "Events,Offroad,lang:english"`,
标签不区分大小写。 + +你可以从以下列表中选择: + +- 年龄/内容: + + - `Mature/18+` + +- 游戏玩法类型: + + - `Freeroam` + - `Roleplay` + - `Economy` + - `Traffic` + +- 比赛类型: + + - `Racing` + - `Racing:NASCAR` + - `Racing:Track` + - `Racing:Drag` + - `Racing:Rally` + - `Touge` + +- 越野活动与挑战: + + - `Offroad` + - `Crawling` + - `Dakar` + - `Challenge` + +- 破坏事件: + + - `Derby` + - `Arena` + +- 天气与时间状况: + + - `Snow/Ice` + - `Rain` + - `Night` + - `Weather` + +- 游戏模式: + + - `Gamemode` + - `Gamemode:Racing` + - `Gamemode:Rally` + - `Gamemode:Drag` + - `Gamemode:Derby` + - `Gamemode:Infection` + - `Gamemode:Cops-Robbers` + - `Gamemode:Delivery` + - `Gamemode:Sumo` + +- 社区与活动: + + - `Scenarios` + - `Events` + - `Leaderboard` + +- 模组: + + - `Modded` + - `Mod:BeamPaint` + - `Mod:BeamJoy` + - `Mod:CEI` + +- 语言: + + - `Lang:English` + - `Lang:Russian` + - `Lang:French` + - `Lang:Spanish` + - `Lang:Portuguese` + - `Lang:German` + - `Lang:Polish` + - `Lang:Arabic` + +- 其他: + + - `Vanilla` + - `Moderated` + +如果某个标签不在此列表中,你可以提交请求要求添加 [在这里](https://forum.beammp.com/t/introducing-server-tags/1320081) + +## Server.log 文件 + +此文件将在服务器运行时生成。它是你在运行服务器时控制台中看到的日志的镜像。
每次需要向我们的支持人员寻求帮助时,都应附上此文件,而且它永远不会显示你的 AuthKey,因此通常无需修改即可直接发送。 + +格式如下($ 前缀表示“变量”,详见下方说明): + +``` +[$DATE $TIME] $CONTEXT [$LOG_LEVEL] $MESSAGE +``` + +地点: + +- `$DATE` 是消息的日期,例如 21/07/2021 +- `$TIME` 是消息的时间,例如 11:05:23 +- `$CONTEXT`(仅在调试模式下可见,主要与开发者相关)是消息的上下文,可能为以下之一: + - `(Player ID) “Player Name”`,其中玩家的ID对管理非常有用 + - 翻译 一个简短的名称,例如 “HeartbeatThread” +- `$LOG_LEVEL` 是消息重要性级别之一: + - `DEBUG`:仅在调试模式下可见,通常信息量很大,仅对开发者重要 + - `INFO`:一般信息 + - `LUA`:来自 Lua 插件的消息 + - `WARN`:描述了通常不应该发生的事情。 + - `ERROR`:发生了非常严重的问题,或者出现了完全意料之外的情况 + - `FATAL`:发生了导致服务器关闭的事件 +- `$MESSAGE` 消息本身,通常是你需要注意并理解的内容。
在某些情况下它可能显得比较隐晦,但一般规则是:只要服务器表面上没有任何明显问题,也没有出现 ERROR,那就一切正常。 + +## 更新服务器 + +### 为什么要更新服务器 + +每次发布新更新时,建议你更新服务器。通常这包括错误修复、稳定性提升和安全性改进,同时还会引入新的通用功能等。 + +要接收更新发布时的新闻,你可以:
关注 Discord 服务器的“update”频道
在论坛上留意相关信息
查看或询问 [GitHub releases 页面](https://github.com/BeamMP/BeamMP-Server/releases) + +### 怎么更新服务器 + +#### 如果你正在使用 BeamMP 合作的托管服务提供商 + +如果你正在使用 BeamMP 合作的托管服务提供商,下面的说明很可能不适用。
我们建议你等待托管服务提供商提供更多详细信息,或者直接联系他们寻求帮助。 + +#### 管理自己的服务器 + +服务器通过将旧的可执行文件替换为新的来更新。
如果你不确定如何操作,下面提供了 Windows 和 Linux 的分步说明。 + +如果你是从源代码构建的,只需重新构建即可。
在重新构建之前,请确保运行以下命令:
`git submodule update --init --recursive` + +#### 在Windows中 + +1. 确保你已安装 [Visual C++ Redistributables](https://aka.ms/vs/17/release/vc_redist.x64.exe),以便运行服务器。 +2. 前往 [BeamMP.com](https://beammp.com/) 并点击“Download Server”按钮。 +3. 下载完成后,你应该会看到一个名为 `BeamMP-Server.exe` 的文件。我们将这个文件称为“新可执行文件”。 +4. 前往你的当前 `BeamMP-Server.exe` 可执行文件所在的文件夹(通常也是 `ServerConfig.toml` 所在的同一文件夹)。我们将这个文件称为“旧可执行文件”。 +5. 将旧的可执行文件替换为新的可执行文件(例如,通过复制或移动新可执行文件到该文件夹中)。 + +#### 在Linux中 + +1. 前往 [BeamMP.com](https://beammp.com/) 并点击“Download Server”按钮,你将被重定向到服务器的 GitHub Releases 页面。 +2. 下载适用于你发行版的正确版本。为方便起见,从现在起我们将它称为 `BeamMP-Server-xxx`,其中 `xxx` 表示你所使用发行版的对应版本。 +3. 下载完成后,你应该会看到一个名为 `BeamMP-Server-xxx` 的文件(具体名称取决于你下载的版本)。
我们将这个文件称为“新可执行文件”。 +4. 前往你的当前 `BeamMP-Server-xxx` 可执行文件所在的文件夹(通常也是 `ServerConfig.toml` 所在的同一文件夹)。
我们将这个文件称为“旧可执行文件”。 +5. 将旧的可执行文件替换为新的可执行文件(例如,通过复制或移动新可执行文件到该文件夹中)。 +6. 在你刚刚替换可执行文件的那个文件夹中打开终端,然后运行以下命令:
`sudo chmod +x BeamMP-Server-xxx`
这将确保服务器可以被运行。 + +### 自动更新 + +服务器(目前)不支持自动更新或更新通知。 + +不过,你可以通过向 GitHub API 查询最新 release 来检查服务器版本与标签的对比。
你可以通过 GET 请求从以下地址获取:
`https://api.github.com/repos/BeamMP/BeamMP-Server/git/refs/tags`