modified: .gitignore

modified:   README.md
	new file:   install.sh
	renamed:    config.py -> lib/constants.py
	renamed:    cvars.py -> lib/cvars.py
	renamed:    formatter.py -> lib/formatter.py
	renamed:    network.py -> lib/network.py
	renamed:    parser.py -> lib/parser.py
	renamed:    qlpycon_config.py -> lib/settings.py
	renamed:    state.py -> lib/state.py
	renamed:    ui.py -> lib/ui.py
	modified:   main.py
	modified:   qlpycon.bash
	new file:   qlpycon.conf.example
This commit is contained in:
xbl
2026-06-13 10:21:29 +02:00
parent be6ae8d137
commit 7ca9795a39
14 changed files with 476 additions and 174 deletions

4
.gitignore vendored
View File

@ -1,8 +1,8 @@
__pycache__/
venv3/
*test*
*.json
*.log
cvarlist.txt
curztest.py
qlpycon.conf
rconpw.txt
CLAUDE*.md

111
README.md
View File

@ -1,81 +1,80 @@
# QLPyCon - Quake Live Python Console
# qlpycon - Quake Live Python Console
Terminal-based client for monitoring and controlling Quake Live servers via ZMQ.
## Features
- Real-time game monitoring (kills, deaths, medals, team switches)
- Server info display (map, gametype, scores, player list)
- Team-aware colorized chat with location tracking
- Powerup pickup and carrier kill notifications
- JSON event capture for analysis
- Quake color code support (^0-^7)
- **Autocomplete for cvars/commands** with fuzzy matching
- **Intelligent argument suggestions** for 25+ commands (bot names, maps, gametypes)
Terminal client for monitoring and controlling Quake Live servers via ZMQ RCON.
## Installation
```bash
pip install pyzmq
python main.py --host tcp://SERVER_IP:PORT --password RCON_PASSWORD
curl -sSL https://6bit.ch/qlpycon/install.sh | bash
```
Or from a cloned repo:
```bash
git clone https://git.6bit.ch/xbl/qlpycon.git
cd qlpycon
./install.sh
```
The installer sets up a virtualenv, installs dependencies, and writes a launcher to `~/.local/bin/qlpycon`.
## Configuration
Edit `qlpycon.conf` in your install directory. See `qlpycon.conf.example` for all options.
```ini
[connection]
password = ${QLPYCON_PASSWORD}
[servers]
ffa = 10.13.12.93:28960
duel = 10.13.12.93:28961
```
## Usage
```bash
# Basic
python main.py --host tcp://SERVER_IP:PORT --password RCON_PASSWORD
# Verbose logging
python main.py --host tcp://SERVER_IP:PORT --password RCON_PASSWORD -v
# Capture JSON events
python main.py --host tcp://SERVER_IP:PORT --password RCON_PASSWORD --json events.log
qlpycon ffa # connect by name
qlpycon --host tcp://10.13.12.93:28960 --password secret # connect directly
qlpycon --list # list configured servers
```
**Options:**
- `--host URI` - ZMQ RCON endpoint (default: tcp://127.0.0.1:27961)
- `--password PASS` - RCON password (required)
- `-v` / `-vv` - Verbose (INFO) or debug (DEBUG) logging
- `--json FILE` - Log all events as JSON
- `--unknown-log FILE` - Log unparsed events (default: unknown_events.log)
- `--host URI` ZMQ RCON endpoint
- `--password PASS` RCON password (or set `QLPYCON_PASSWORD` env var)
- `--list` — list configured servers and exit
- `-v` / `-vv` — verbose (INFO) or debug (DEBUG) logging
- `--json FILE` — log all JSON events to file
- `--unknown-log FILE` — log unparsed events (default: unknown_events.log)
**Configuration File (Optional):**
Create `~/.qlpycon.conf` or `./qlpycon.conf`:
```ini
[connection]
host = tcp://SERVER_IP:PORT
password = your_password
## Features
[logging]
level = INFO
```
- Real-time kill/death/medal/team switch events
- Team-aware colorized output with Quake color code support
- Powerup pickup and carrier kill notifications
- Server info panel (map, gametype, scores, players)
- Tab autocomplete for cvars and commands with fuzzy matching
- Argument suggestions for 25+ commands (bot names, maps, gametypes)
- Command history (↑/↓)
**Input Features:**
- **Smart autocomplete** - Type commands and see arguments highlighted in real-time
- **Argument value suggestions** - Intelligent suggestions for bot names, maps, gametypes, teams, etc.
- **Tab** - Cycle through autocomplete suggestions
- **↑/↓** - Command history navigation
- **Argument highlighting** - Current argument position shown in reverse video
- **Command signatures** - Automatic display (e.g., `addbot <botname> [skill 1-5] [team]`)
See [AUTOCOMPLETE.md](AUTOCOMPLETE.md) for details.
See [AUTOCOMPLETE.md](AUTOCOMPLETE.md) for input details.
## Architecture
```
main.py - Main loop, argument parsing, signal handling
config.py - Constants (weapons, teams, colors, limits)
state.py - Game state (ServerInfo, PlayerTracker, EventDeduplicator)
network.py - ZMQ connections (RCON DEALER, Stats SUB sockets)
parser.py - JSON event parsing (deaths, medals, switches, stats)
formatter.py - Message formatting, color codes, team prefixes
ui.py - Curses interface (3-panel: info, output, input)
main.py — entry point, arg parsing, signal handling
qlpycon.conf — user configuration
lib/
constants.py — weapons, teams, colors, limits
settings.py — config file loader
state.py — game state (server info, players, teams)
network.py — ZMQ connections (RCON DEALER, stats SUB)
parser.py — JSON event parsing
formatter.py — message formatting and colorization
ui.py — curses interface (info / output / input panels)
cvars.py — cvar/command database and autocomplete
```
**Supported Events:**
PLAYER_SWITCHTEAM, PLAYER_DEATH/KILL, PLAYER_MEDAL, PLAYER_STATS, MATCH_STARTED/REPORT, PLAYER_CONNECT/DISCONNECT, ROUND_OVER
## License
WTFPL

218
install.sh Executable file
View File

@ -0,0 +1,218 @@
#!/usr/bin/env bash
#
# qlpycon installer
# Usage:
# curl -sSL https://6bit.ch/qlpycon/install.sh | bash
#
# or run directly from cloned repo:
# git clone https://git.6bit.ch/xbl/qlpycon.git
# cd qlpycon
# chmod u+x install.sh
# ./install.sh
#
set -e
TAR_URL="https://6bit.ch/qlpycon/qlpycon.tar.gz"
BIN_DIR="$HOME/.local/bin"
BIN_PATH="$BIN_DIR/qlpycon"
# === Colors ===>
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'
info() { echo -e "${CYAN} o_o [INFO]${NC} $*"; }
success() { echo -e "${GREEN} ^_^ [SUCCESS]${NC} $*"; }
warn() { echo -e "${YELLOW} >_< [WARN]${NC} $*"; }
die() { echo -e "${RED} x_x [ERROR]${NC} $*" >&2; exit 1; }
# === Checks ===>
check_dependencies() {
info "Checking dependencies..."
command -v python3 >/dev/null 2>&1 || die "python3 is required but not installed"
# Check Python version >= 3.9
python3 -c "
import sys
if sys.version_info < (3, 9):
print('Python 3.9+ required, found ' + sys.version)
sys.exit(1)
" || die "Python 3.9 or higher is required"
command -v pip3 >/dev/null 2>&1 || \
python3 -m pip --version >/dev/null 2>&1 || \
die "pip is required but not installed"
success "Dependencies OK"
}
# === Source detection ===>
detect_source() {
if [[ -f "./main.py" && -d "./lib" ]]; then
echo "repo"
else
echo "download"
fi
}
# === Download ===>
download_and_extract() {
info "Downloading to temp and extracting to $INSTALL_DIR..."
mkdir -p "$INSTALL_DIR"
TMP_TAR=$(mktemp)
if command -v curl >/dev/null 2>&1; then
curl -sSL "$TAR_URL" -o "$TMP_TAR" || die "Download failed"
else
wget -qO "$TMP_TAR" "$TAR_URL" || die "Download failed"
fi
# Preserve existing config if updating
if [[ -f "$INSTALL_DIR/qlpycon.conf" ]]; then
warn "Existing qlpycon.conf found - keeping it"
tar -xzf "$TMP_TAR" -C "$INSTALL_DIR" --exclude='qlpycon.conf'
else
tar -xzf "$TMP_TAR" -C "$INSTALL_DIR"
fi
rm -f "$TMP_TAR"
success "Extracted to $INSTALL_DIR"
}
# === Venv ===>
setup_venv() {
info "Setting up Python virtual environment..."
if [[ ! -d "$INSTALL_DIR/venv" ]]; then
python3 -m venv "$INSTALL_DIR/venv" || die "Failed to create venv"
else
warn "venv already exists - skipping creation"
fi
info "Installing pip"
"$INSTALL_DIR/venv/bin/pip" install --quiet --upgrade pip
info "Installing pyzmq via pip"
"$INSTALL_DIR/venv/bin/pip" install --quiet pyzmq
success "Virtual environment ready"
}
# === Launcher ===>
write_launcher() {
info "Writing launcher to $BIN_PATH..."
mkdir -p "$BIN_DIR"
cat > "$BIN_PATH" << EOF
#!/usr/bin/env bash
# qlpycon launcher — generated by install.sh
QLPYCON_DIR="$INSTALL_DIR"
if [[ ! -d "\$QLPYCON_DIR" ]]; then
echo "Error: qlpycon directory not found: \$QLPYCON_DIR"
echo "Re-run install.sh or cry :("
exit 1
fi
cd "\$QLPYCON_DIR"
source "\$QLPYCON_DIR/venv/bin/activate"
exec python3 main.py "\$@"
EOF
chmod +x "$BIN_PATH"
success "Launcher written to $BIN_PATH"
}
# === PATH check ===>
check_path() {
info "Checking \$PATH..."
if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
warn "$BIN_DIR is not in your PATH."
echo ""
echo " Add this to your ~/.bashrc or ~/.zshrc:"
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
echo ""
else
success "\$PATH is superfine"
fi
}
# === Config ===>
setup_config() {
if [[ ! -f "$INSTALL_DIR/qlpycon.conf" ]]; then
if [[ -f "$INSTALL_DIR/qlpycon.conf.example" ]]; then
cp "$INSTALL_DIR/qlpycon.conf.example" "$INSTALL_DIR/qlpycon.conf"
success "Created $INSTALL_DIR/qlpycon.conf from example"
fi
fi
}
# === Main ===>
main() {
print_banner() {
cat <<'EOF'
_/
_/_/_/ _/ _/_/_/ _/ _/ _/_/_/ _/_/ _/_/_/
_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/
_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/
_/_/_/ _/ _/_/_/ _/_/_/ _/_/_/ _/_/ _/ _/
_/ _/ _/
_/ _/ _/_/
EOF
}
if command -v lolcat >/dev/null 2>&1 && [[ ${LOLCAT:-1} -eq 1 ]]; then
{
print_banner
echo
echo " qlpycon installer"
} | lolcat -
else
echo -e "${MAGENTA}"
print_banner
echo -e "${NC}"
echo -e "${CYAN} qlpycon installer${NC}"
fi
echo
check_dependencies
SOURCE=$(detect_source)
if [[ "$SOURCE" == "repo" ]]; then
INSTALL_DIR="$(pwd)"
info "Running from cloned repo - using $INSTALL_DIR"
else
DEFAULT_DIR="$HOME/.local/share/qlpycon"
read -p "Install directory [$DEFAULT_DIR]: " USER_DIR </dev/tty
INSTALL_DIR="${USER_DIR:-$DEFAULT_DIR}"
INSTALL_DIR="${INSTALL_DIR/#\~/$HOME}"
download_and_extract
fi
setup_venv
setup_config
write_launcher
check_path
echo ""
success "qlpycon installed"
echo ""
echo " Edit your config: ${INSTALL_DIR}/qlpycon.conf"
echo " Run: qlpycon --list"
echo " qlpycon ffa"
echo " qlpycon --host tcp://1.2.3.4:28960 --password secret"
echo ""
}
main "$@"

View File

@ -6,7 +6,7 @@ Handles Quake color codes and team prefixes
import re
import time
from config import TEAM_COLORS, COLOR_CODE_PATTERN, SPECIAL_CHAR
from .constants import TEAM_COLORS, COLOR_CODE_PATTERN, SPECIAL_CHAR
def strip_color_codes(text):
@ -169,7 +169,7 @@ def format_powerup_message(message, player_tracker):
Format powerup pickup and carrier kill messages
Returns formatted message or None if not a powerup message
"""
from config import POWERUP_COLORS
from .constants import POWERUP_COLORS
import time
if message.startswith("broadcast:"):

View File

@ -7,8 +7,8 @@ Parses events from Quake Live stats stream
import json
import logging
import time
from config import WEAPON_NAMES, WEAPON_KILL_NAMES, DEATH_MESSAGES
from formatter import get_team_prefix, strip_color_codes
from .constants import WEAPON_NAMES, WEAPON_KILL_NAMES, DEATH_MESSAGES
from .formatter import get_team_prefix, strip_color_codes
logger = logging.getLogger('parser')

View File

@ -82,11 +82,50 @@ class ConfigLoader:
if not password:
return None
# Support environment variable substitution: ${VAR_NAME}
if password.startswith('${') and password.endswith('}'):
env_var = password[2:-1]
return os.environ.get(env_var)
return self._resolve_password(password)
def get_servers(self):
"""Return dict of name -> host:port from [servers]"""
if not self.config.has_section('servers'):
return {}
return dict(self.config.items('servers'))
def get_server(self, name):
"""
Resolve a named server to (host, password).
host comes from [servers], password from [server:name] or [connection].
Returns (host, password) or (None, None) if name not found.
"""
servers = self.get_servers()
if name not in servers:
return None, None
host = servers[name]
if not host.startswith('tcp://'):
host = f'tcp://{host}'
# Per-server password override
section = f'server:{name}'
if self.config.has_section(section):
password = self.config.get(section, 'password', fallback=None)
if password:
password = self._resolve_password(password)
else:
password = self.get_password()
return host, password
def _resolve_password(self, password):
"""Resolve a password string, expanding ${VAR:-default} if needed"""
if not password:
return None
if password.startswith('${') and password.endswith('}'):
inner = password[2:-1]
if ':-' in inner:
env_var, default = inner.split(':-', 1)
else:
env_var, default = inner, None
return os.environ.get(env_var, default)
return password
def get_log_level(self):
@ -104,29 +143,52 @@ class ConfigLoader:
def create_example_config():
"""Create an example configuration file"""
config_content = """# QLPyCon Configuration File
# Place this file as ~/.qlpycon.conf or ./qlpycon.conf
config_content = """# qlpycon.conf
# Edit this file as needed.
#
# Connect by server name: qlpycon ffa
# Connect directly: qlpycon --host tcp://1.2.3.4:28960 --password secret
# List servers: qlpycon --list
# === Connection defaults ===>
# Default host if no server name or --host is given
[connection]
# Server connection settings
host = tcp://10.13.12.93:28969
# Use ${ENV_VAR} to read from environment
password = ${QLPYCON_PASSWORD}
host = tcp://127.0.0.1:28960
# Password for all servers unless overridden in [server:name]
# Use ${ENV_VAR} to read from environment variable (recommended)
# Or set directly (less secure):
password = ${QLPYCON_PASSWORD:-secret}
# === Named servers ===>
# Simple entries: name = host:port
# These use the password from [connection] above.
[servers]
# Example:
#ffa = 10.13.12.161:28960
# === Per-server overrides ===>
# Use [server:name] to override any setting for a specific server.
# The name must match an entry in [servers] above.
# Example:
#[server:ffa]
#password = ${FFA_PASSWORD:-secret}
# === Logging ===>
[logging]
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
level = INFO
level = WARNING
# === UI ===>
[ui]
# Max command history entries
# Number of commands to remember in history
max_history = 10
# Color scheme (future feature)
color_scheme = quake
# === Behaviour ===>
[behavior]
# Quit confirmation timeout (seconds)
# Seconds to confirm quit (press Ctrl-C twice within this window)
quit_timeout = 3.0
# Player respawn delay (seconds)
# Seconds before players respawn after death
respawn_delay = 3.0
"""

View File

@ -5,8 +5,8 @@ Tracks server info, players, and teams
"""
import logging
from config import TEAM_MODES, TEAM_MAP, MAX_RECENT_EVENTS
from formatter import strip_color_codes
from .constants import TEAM_MODES, TEAM_MAP, MAX_RECENT_EVENTS
from .formatter import strip_color_codes
logger = logging.getLogger('state')

View File

@ -10,8 +10,8 @@ import threading
import queue
import logging
import time
from config import COLOR_PAIRS, INFO_WINDOW_HEIGHT, INFO_WINDOW_Y, OUTPUT_WINDOW_Y, INPUT_WINDOW_HEIGHT, TEAM_MODES, MAX_COMMAND_HISTORY
from cvars import autocomplete, COMMAND_SIGNATURES, get_signature_with_highlight, get_argument_suggestions, COMMAND_ARGUMENTS
from .constants import COLOR_PAIRS, INFO_WINDOW_HEIGHT, INFO_WINDOW_Y, OUTPUT_WINDOW_Y, INPUT_WINDOW_HEIGHT, TEAM_MODES, MAX_COMMAND_HISTORY
from .cvars import autocomplete, COMMAND_SIGNATURES, get_signature_with_highlight, get_argument_suggestions, COMMAND_ARGUMENTS
logger = logging.getLogger('ui')
@ -729,7 +729,7 @@ class UIManager:
red = f"{red_score:>3} {'^8^1X^7^0 ' if red_dead else ''}{red_name}" if red_name else ''
blue = f"{blue_score:>3} {'^8^1X^7^0 ' if blue_dead else ''}{blue_name}" if blue_name else ''
from formatter import strip_color_codes
from .formatter import strip_color_codes
red_clean = strip_color_codes(red)
blue_clean = strip_color_codes(blue)
@ -770,7 +770,7 @@ class UIManager:
col1 = f"{col1_score:>3} {'^8^1X^7^0 ' if col1_dead else ''}{col1_name}" if col1_name else ''
col2 = f"{col2_score:>3} {'^8^1X^7^0 ' if col2_dead else ''}{col2_name}" if col2_name else ''
from formatter import strip_color_codes
from .formatter import strip_color_codes
col1_clean = strip_color_codes(col1)
col2_clean = strip_color_codes(col2)

87
main.py
View File

@ -16,13 +16,13 @@ import sys
import os
import threading
from config import VERSION, DEFAULT_HOST, POLL_TIMEOUT, QUIT_CONFIRM_TIMEOUT, RESPAWN_DELAY, MAX_COMMAND_HISTORY
from state import GameState
from network import RconConnection, StatsConnection
from parser import EventParser
from formatter import format_message, format_chat_message, format_powerup_message, strip_color_codes
from ui import UIManager
from qlpycon_config import ConfigLoader
from lib.constants import VERSION, DEFAULT_HOST, POLL_TIMEOUT, QUIT_CONFIRM_TIMEOUT, RESPAWN_DELAY, MAX_COMMAND_HISTORY
from lib.state import GameState
from lib.network import RconConnection, StatsConnection
from lib.parser import EventParser
from lib.formatter import format_message, format_chat_message, format_powerup_message, strip_color_codes
from lib.ui import UIManager
from lib.settings import ConfigLoader
# Pre-compiled regex patterns
CVAR_RESPONSE_PATTERN = re.compile(r'"([^"]+)"\s+is:"([^"]*)"')
@ -299,32 +299,12 @@ def parse_player_events(message, game_state, ui):
return False
def main_loop(screen):
def main_loop(screen, args):
"""Main application loop"""
# Setup signal handler for Ctrl+C with confirmation
signal.signal(signal.SIGINT, signal_handler)
# Load configuration file (optional)
config = ConfigLoader()
config.load()
# Parse arguments (command line overrides config file)
parser = argparse.ArgumentParser(description='Verbose QuakeLive server statistics')
parser.add_argument('--host', default=config.get_host() or DEFAULT_HOST,
help=f'ZMQ URI to connect to. Defaults to {DEFAULT_HOST}')
parser.add_argument('--password', default=config.get_password(), required=False,
help='RCON password')
parser.add_argument('--identity', default=uuid.uuid1().hex,
help='Socket identity (random UUID by default)')
parser.add_argument('-v', '--verbose', action='count', default=0,
help='Increase verbosity (-v INFO, -vv DEBUG)')
parser.add_argument('--unknown-log', default='unknown_events.log',
help='File to log unknown JSON events')
parser.add_argument('-j', '--json', dest='json_log', default=None,
help='File to log all JSON events')
args = parser.parse_args()
# Set logging level
if args.verbose == 0:
logger.setLevel(logging.WARNING)
@ -356,6 +336,7 @@ def main_loop(screen):
ui.print_message(f"zmq python bindings {zmq.__version__}, libzmq version {zmq.zmq_version()}\n")
# Initialize network connections
ui.print_message(f"Connecting with host={args.host} password={args.password}\n")
rcon = RconConnection(args.host, args.password, args.identity)
rcon.connect()
@ -546,4 +527,52 @@ def main_loop(screen):
logger.info("Shutdown complete")
if __name__ == '__main__':
curses.wrapper(main_loop)
# Load config
config = ConfigLoader()
config.load()
# Parse arguments
parser = argparse.ArgumentParser(description='Quake Live Python Console')
parser.add_argument('server', nargs='?', default=None,
help='Named server from qlpycon.conf (e.g. ffa, duel)')
parser.add_argument('--host', default=None,
help='ZMQ URI to connect to (e.g. tcp://1.2.3.4:28960)')
parser.add_argument('--password', default=None,
help='RCON password')
parser.add_argument('--list', action='store_true',
help='List configured servers and exit')
parser.add_argument('--identity', default=uuid.uuid1().hex,
help='Socket identity (random UUID by default)')
parser.add_argument('-v', '--verbose', action='count', default=0,
help='Increase verbosity (-v INFO, -vv DEBUG)')
parser.add_argument('--unknown-log', default='unknown_events.log',
help='File to log unknown JSON events')
parser.add_argument('-j', '--json', dest='json_log', default=None,
help='File to log all JSON events')
args = parser.parse_args()
# Handle --list
if args.list:
servers = config.get_servers()
if not servers:
print('No servers configured in qlpycon.conf')
else:
print('Configured servers:')
for name, host in servers.items():
print(f' {name:<12} {host}')
sys.exit(0)
# Resolve host and password
if args.server:
host, password = config.get_server(args.server)
if host is None:
print(f"Error: server '{args.server}' not found in qlpycon.conf")
print("Use 'qlpycon --list' to see configured servers.")
sys.exit(1)
args.host = args.host or host
args.password = args.password or password
else:
args.host = args.host or config.get_host() or DEFAULT_HOST
args.password = args.password or config.get_password()
curses.wrapper(main_loop, args)

View File

@ -1,66 +1,13 @@
#!/usr/bin/env bash
#
# Helper script to connect to different Quake Live servers
# Required: QLPYCON_PASSWORD
# Optional: QLPYCON_WORKDIR (default: /home/marc/git/qlpycon.git)
# QLPYCON_SERVERIP (default: 10.13.12.93)
# qlpycon launcher — generated by install.sh
QLPYCON_DIR="/home/xbl/gitz/qlpycon"
# Required
password="${QLPYCON_PASSWORD:-}"
workdir="${QLPYCON_WORKDIR:-/home/xbl/gitz/qlpycon}"
serverip="${QLPYCON_SERVERIP:-10.13.12.93}"
# Validate
if [ -z "$password" ]; then
echo "Error: QLPYCON_PASSWORD not set"
echo "Set: export QLPYCON_PASSWORD='your_password'"
if [[ ! -d "$QLPYCON_DIR" ]]; then
echo "Error: qlpycon directory not found: $QLPYCON_DIR"
echo "Re-run install.sh or cry :("
exit 1
fi
if [ ! -d "$workdir" ]; then
echo "Error: Working directory does not exist: $workdir"
exit 1
fi
if [ ! -d "$workdir/venv" ]; then
echo "Error: venv not found in $workdir"
exit 1
fi
cd "$workdir" || exit 1
source venv/bin/activate
if [ "$1" == "ffa" ]; then
python3 main.py --host tcp://"$serverip":28960 --password "$password"
elif [ "$1" == "duel" ]; then
python3 main.py --host tcp://"$serverip":28961 --password "$password"
elif [ "$1" == "rcpma" ]; then
python3 main.py --host tcp://"$serverip":28962 --password "$password"
elif [ "$1" == "iffa" ]; then
python3 main.py --host tcp://"$serverip":28963 --password "$password"
elif [ "$1" == "ca" ]; then
python3 main.py --host tcp://"$serverip":28964 --password "$password"
elif [ "$1" == "ctf" ]; then
python3 main.py --host tcp://"$serverip":28965 --password "$password"
elif [ "$1" == "rcvq3" ]; then
python3 main.py --host tcp://"$serverip":28966 --password "$password"
elif [ "$1" == "test" ]; then
python3 main.py --host tcp://"$serverip":28967 --password "$password"
elif [ "$1" == "ft" ]; then
python3 main.py --host tcp://"$serverip":28968 --password "$password"
elif [ "$1" == "leduel" ]; then
python3 main.py --host tcp://"$serverip":28969 --password "$password"
else
echo "[ ffa (28960), duel (28961), rcpma (28962), iffa (28963), ca (28964), ctf (28965), rcvq3 (28966), test (28967), ft (28968), leduel (28969) ]"
fi
cd "$QLPYCON_DIR"
source "$QLPYCON_DIR/venv/bin/activate"
exec python3 main.py "$@"

47
qlpycon.conf.example Normal file
View File

@ -0,0 +1,47 @@
# qlpycon.conf
# Edit this file as needed.
#
# Connect by server name: qlpycon ffa
# Connect directly: qlpycon --host tcp://1.2.3.4:28960 --password secret
# List servers: qlpycon --list
# === Connection defaults ===>
# Default host if no server name or --host is given
[connection]
host = tcp://127.0.0.1:28960
# Password for all servers unless overridden in [server:name]
# Use ${ENV_VAR} to read from environment variable (recommended)
# Or set directly (less secure):
password = ${QLPYCON_PASSWORD:-secret}
# === Named servers ===>
# Simple entries: name = host:port
# These use the password from [connection] above.
[servers]
# Example:
#ffa = 10.13.12.161:28960
# === Per-server overrides ===>
# Use [server:name] to override any setting for a specific server.
# The name must match an entry in [servers] above.
# Example:
#[server:ffa]
#password = ${FFA_PASSWORD:-secret}
# === Logging ===>
[logging]
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
level = WARNING
# === UI ===>
[ui]
# Number of commands to remember in history
max_history = 10
# === Behaviour ===>
[behavior]
# Seconds to confirm quit (press Ctrl-C twice within this window)
quit_timeout = 3.0
# Seconds before players respawn after death
respawn_delay = 3.0