Skip to content

Commit

Permalink
Implement command line launch of the auto lobby for LAN games (#6474)
Browse files Browse the repository at this point in the history
  • Loading branch information
lL1l1 authored Oct 14, 2024
1 parent c95b42a commit 6780001
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 21 deletions.
2 changes: 2 additions & 0 deletions changelog/snippets/other.6474.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- (#6474) Implement command line launch of the auto lobby for LAN games. This allows rapidly testing multiplayer in a local environment.
Further details on the command line switches used and an example script for launching multiple instances are in the linked pull request.
8 changes: 4 additions & 4 deletions engine/User.lua
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,13 @@ end
function GetCamera(name)
end

--- Gets the following arguments to a commandline option. For example, if `/arg -flag key:value drop`
--- was passed to the commandline, then `GetCommandLineArg("/arg", 2)` would return
--- `{"-flag", "key:value"}`
--- Gets the "arguments" (tokens split by spaces) that follow a commandline option,
--- disregarding if they start with `/` like other commandline options.
--- Returns `false` if there are not `maxArgs` tokens after the `option`.
---@see GetCommandLineArgTable(option) for parsing key-values
---@param option string
---@param maxArgs number
---@return string[]?
---@return string[] | false
function GetCommandLineArg(option, maxArgs)
end

Expand Down
9 changes: 5 additions & 4 deletions lua/system/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -731,12 +731,13 @@ end
-- GetCommandLineArgTable("/arg") -> {key1="value1", key2="value2"}
function GetCommandLineArgTable(option)
-- Find total number of args
local next = 1
local nextMax = 1
local args, nextArgs = nil, nil
repeat
nextArgs, args = GetCommandLineArg(option, next), nextArgs
next = next + 1
until not nextArgs
nextArgs, args = GetCommandLineArg(option, nextMax), nextArgs
nextMax = nextMax + 1
-- GetCommandLineArg tokenizes without being limited by `/`, so we need to stop manually
until not nextArgs or nextArgs[nextMax - 1]:sub(0,1) == "/"

-- Construct result table
local result = {}
Expand Down
11 changes: 6 additions & 5 deletions lua/ui/lobby/autolobby.lua
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ local function MakeLocalPlayerInfo(name)
local result = LobbyComm.GetDefaultPlayerOptions(name)
result.Human = true

-- Game must have factions for players or else it won't start, so default to UEF.
result.Faction = 1
local factionData = import("/lua/factions.lua")

for index, tbl in factionData.Factions do
if HasCommandLineArg("/" .. tbl.Key) then
result.Faction = index
Expand All @@ -104,9 +105,9 @@ local function MakeLocalPlayerInfo(name)
result.Team = tonumber(GetCommandLineArg("/team", 1)[1])
result.StartSpot = tonumber(GetCommandLineArg("/startspot", 1)[1]) or false

result.DEV = tonumber(GetCommandLineArg("/deviation", 1)[1]) or ""
result.MEAN = tonumber(GetCommandLineArg("/mean", 1)[1]) or ""
result.NG = tonumber(GetCommandLineArg("/numgames", 1)[1]) or ""
result.DEV = tonumber(GetCommandLineArg("/deviation", 1)[1] or 500)
result.MEAN = tonumber(GetCommandLineArg("/mean", 1)[1] or 1500)
result.NG = tonumber(GetCommandLineArg("/numgames", 1)[1] or 0)
result.DIV = (GetCommandLineArg("/division", 1)[1]) or ""
result.SUBDIV = (GetCommandLineArg("/subdivision", 1)[1]) or ""
result.PL = math.floor(result.MEAN - 3 * result.DEV)
Expand Down Expand Up @@ -447,7 +448,7 @@ end

-- join an already existing lobby
function JoinGame(address, asObserver, playerName, uid)
LOG("Joingame (name=" .. playerName .. ", uid=" .. uid .. ", address=" .. address ..")")
LOG("Joingame (name=" .. tostring(playerName) .. ", uid=" .. tostring(uid) .. ", address=" .. tostring(address) ..")")
CreateUI()

-- TODO: I'm not sure if this argument is passed along when you are joining a lobby
Expand Down
44 changes: 36 additions & 8 deletions lua/ui/uimain.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,46 @@ function StartFrontEndUI()
end
end


--Used by command line to host a game
function StartHostLobbyUI(protocol, port, playerName, gameName, mapName, natTraversalProvider)
LOG("Command line hostlobby")
local lobby = import("/lua/ui/lobby/lobby.lua")
--- Hosts a multiplayer LAN lobby.
--- Called by the engine with the command line argument `/hostgame <protocol> <port> <playerName> <gameName> <mapFile>`
--- Add the argument `/players <number>` to use the auto lobby. `/<factionName>` to choose a faction.
---@param protocol UILobbyProtocols
---@param port number
---@param playerName string
---@param gameName string
---@param mapFile FileName
---@param natTraversalProvider userdata?
function StartHostLobbyUI(protocol, port, playerName, gameName, mapFile, natTraversalProvider)
LOG("Hosting lobby from the command line")
local lobby
-- auto lobby only works with 2+ players
local autoStart = GetCommandLineArg("/players", 1)[1] >= 2
if autoStart then
lobby = import("/lua/ui/lobby/autolobby.lua")
else
lobby = import("/lua/ui/lobby/lobby.lua")
end
lobby.CreateLobby(protocol, port, playerName, nil, natTraversalProvider, GetFrame(0), StartFrontEndUI)
lobby.HostGame(gameName, mapName, false)
lobby.HostGame(gameName, mapFile, false)
end

--Used by command line to join a game
--- Joins a multiplayer lobby.
--- Called by the engine with the command line argument `/joingame <protocol> <address> <playerName>`
--- Add the argument `/players <number>` to use the auto lobby. `/<factionName>` to choose a faction.
---@param protocol UILobbyProtocols
---@param address string
---@param playerName string
---@param natTraversalProvider userdata?
function StartJoinLobbyUI(protocol, address, playerName, natTraversalProvider)
local lobby = import("/lua/ui/lobby/lobby.lua")
LOG("Joining lobby from the command line") -- can also be from lobby.lua ReturnToMenu(true), but that never gets called
local lobby
-- auto lobby only works with 2+ players
local autoStart = GetCommandLineArg("/players", 1)[1] >= 2
if autoStart then
lobby = import("/lua/ui/lobby/autolobby.lua")
else
lobby = import("/lua/ui/lobby/lobby.lua")
end
local port = 0
lobby.CreateLobby(protocol, port, playerName, nil, natTraversalProvider, GetFrame(0), StartFrontEndUI)
lobby.JoinGame(address, false)
Expand Down
116 changes: 116 additions & 0 deletions scripts/LaunchFAInstances.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
param (
[int]$players = 2, # Default to 2 instances (1 host, 1 client)
[string]$map = "/maps/scmp_009/SCMP_009_scenario.lua", # Default map: Seton's Clutch
[int]$port = 15000, # Default port for hosting the game
[int]$teams = 2 # Default to two teams, 0 for FFA
)

# Base path to the bin directory
$binPath = "C:\ProgramData\FAForever\bin"

# Paths to the potential executables within the base path
$debuggerExecutable = Join-Path $binPath "FAFDebugger.exe"
$regularExecutable = Join-Path $binPath "ForgedAlliance.exe"

# Check for the existence of the executables and choose accordingly
if (Test-Path $debuggerExecutable) {
$gameExecutable = $debuggerExecutable
Write-Output "Using debugger executable: $gameExecutable"
} elseif (Test-Path $regularExecutable) {
$gameExecutable = $regularExecutable
Write-Output "Debugger not found, using regular executable: $gameExecutable"
} else {
Write-Output "Neither debugger nor regular executable found in $binPath. Exiting script."
exit 1
}

# Command-line arguments common for all instances
$baseArguments = '/init "init_dev.lua" /EnableDiskWatch /nomovie /RunWithTheWind /gameoptions CheatsEnabled:true GameSpeed:adjustable '

# Game-specific settings
$hostProtocol = "udp"
$hostPlayerName = "HostPlayer_1"
$gameName = "MyGame"

# Array of factions to choose from
$factions = @("UEF", "Seraphim", "Cybran", "Aeon")

# Get the screen resolution (for placing and resizing the windows)
Add-Type -AssemblyName System.Windows.Forms
$screenWidth = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width
$screenHeight = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height

# Calculate the number of rows and columns for the grid layout
$columns = [math]::Ceiling([math]::Sqrt($players))
$rows = [math]::Ceiling($players / $columns)

# Calculate the size of each window based on the grid
# Limit the window size to 1024x768 as the game session will not launch if it is smaller
$windowWidth = [math]::Max([math]::Floor($screenWidth / $columns), 1024)
$windowHeight = [math]::Max([math]::Floor($screenHeight / $rows), 768)

# Function to launch a single game instance
function Launch-GameInstance {
param (
[int]$instanceNumber,
[int]$xPos,
[int]$yPos,
[string]$arguments
)

# Add window position and size arguments
$arguments += " /position $xPos $yPos /size $windowWidth $windowHeight"

try {
Start-Process -FilePath $gameExecutable -ArgumentList $arguments -NoNewWindow
Write-Host "Launched instance $instanceNumber at position ($xPos, $yPos) with size ($windowWidth, $windowHeight) and arguments: $arguments"
} catch {
Write-Host "Failed to launch instance ${instanceNumber}: $_"
}
}

# Function to calculate team argument based on instance number and team configuration
function Get-TeamArgument {
param (
[int]$instanceNumber
)

if ($teams -eq 0) {
return "" # No team argument for FFA
}

# Calculate team number; additional +1 because player team indices start at 2
return "/team $((($instanceNumber % $teams) + 1 + 1))"
}

# Prepare arguments and launch instances
if ($players -eq 1) {
$logFile = "dev.log"
Launch-GameInstance -instanceNumber 1 -xPos 0 -yPos 0 -arguments "/log $logFile /showlog /map $map $baseArguments"
} else {
$hostLogFile = "host_dev_1.log"
$hostFaction = $factions | Get-Random
$hostTeamArgument = Get-TeamArgument -instanceNumber 0
$hostArguments = "/log $hostLogFile /showlog /hostgame $hostProtocol $port $hostPlayerName $gameName $map /startspot 1 /players $players /$hostFaction $hostTeamArgument $baseArguments"

# Launch host game instance
Launch-GameInstance -instanceNumber 1 -xPos 0 -yPos 0 -arguments $hostArguments

# Client game instances
for ($i = 1; $i -lt $players; $i++) {
$row = [math]::Floor($i / $columns)
$col = $i % $columns
$xPos = $col * $windowWidth
$yPos = $row * $windowHeight

$clientLogFile = "client_dev_$($i + 1).log"
$clientPlayerName = "ClientPlayer_$($i + 1)"
$clientFaction = $factions | Get-Random
$clientTeamArgument = Get-TeamArgument -instanceNumber $i
$clientArguments = "/log $clientLogFile /joingame $hostProtocol localhost:$port $clientPlayerName /startspot $($i + 1) /players $players /$clientFaction $clientTeamArgument $baseArguments"

Launch-GameInstance -instanceNumber ($i + 1) -xPos $xPos -yPos $yPos -arguments $clientArguments
}
}

Write-Host "$players instance(s) of the game launched. Host is running at port $port."

0 comments on commit 6780001

Please sign in to comment.