This commit is contained in:
xbl
2025-12-23 10:18:50 +01:00
parent 6b4445b538
commit c648a99439
3 changed files with 18 additions and 42 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
__pycache__/ __pycache__/
venv3/ venv3/
.gitignore
*.log *.log

View File

@ -1,8 +1,6 @@
# QLPyCon - Quake Live Python Console # QLPyCon - Quake Live Python Console
A modular, refactored terminal-based client for monitoring Quake Live servers via ZMQ. A modular, refactored terminal-based client for monitoring and remote controlling Quake Live servers via ZMQ.
## Version 0.8.0 - Modular Architecture
### Features ### Features
@ -93,15 +91,6 @@ python main.py --unknown-log my_unknown.log
- Threaded input queue for non-blocking commands - Threaded input queue for non-blocking commands
- Color rendering with curses - Color rendering with curses
### Key Improvements Over v0.7.0
1. **Modular Design** - Separated concerns into focused modules
2. **Clean Classes** - OOP design for state and connections
3. **Better Maintainability** - Each module has a single responsibility
4. **Easier Testing** - Components can be tested independently
5. **Clear Interfaces** - Well-defined APIs between modules
6. **Improved Comments** - Every module, class, and function documented
### Event Types Supported ### Event Types Supported
- `PLAYER_SWITCHTEAM` - Team changes - `PLAYER_SWITCHTEAM` - Team changes
@ -125,20 +114,6 @@ Quake Live uses `^N` color codes where N is 0-7:
- `^6` - Magenta - `^6` - Magenta
- `^7` - White (default) - `^7` - White (default)
### Development
To extend QLPyCon:
1. **Add new event types** - Edit `parser.py`, add handler method
2. **Change UI layout** - Edit `ui.py`, modify window creation
3. **Add new commands** - Edit `main.py`, handle in main loop
4. **Modify formatting** - Edit `formatter.py`, adjust color/timestamp logic
5. **Add configuration** - Edit `config.py`, add constants
### License ### License
MIT License - See original qlpycon.py for details WTFPL
### Credits
Refactored from qlpycon.py v0.7.0

30
ui.py
View File

@ -36,38 +36,40 @@ class CursesHandler(logging.Handler):
except: except:
self.handleError(record) self.handleError(record)
def print_colored(window, message, attributes=0): def print_colored(window, message, attributes=0):
""" """
Print message with Quake color codes (^N) Print message with Quake color codes (^N)
^0 = black, ^1 = red, ^2 = green, ^3 = yellow, ^4 = blue, ^5 = cyan, ^6 = magenta, ^7 = white ^0 = bold, ^1 = red, ^2 = green, ^3 = yellow, ^4 = blue, ^5 = cyan, ^6 = magenta, ^7 = white/reset
""" """
if not curses.has_colors: if not curses.has_colors:
window.addstr(message) window.addstr(message)
return return
color = 0 color = 0
bold = False
parse_color = False parse_color = False
for ch in message: for ch in message:
val = ord(ch) val = ord(ch)
if parse_color: if parse_color:
if ord('0') <= val <= ord('7'): if ch == '0':
bold = True
elif ch == '7':
color = 0
bold = False
elif ord('1') <= val <= ord('6'):
color = val - ord('0') color = val - ord('0')
if color == 7:
color = 0
else: else:
window.addch('^', curses.color_pair(color) | attributes) window.addch('^', curses.color_pair(color) | (curses.A_BOLD if bold else 0) | attributes)
window.addch(ch, curses.color_pair(color) | attributes) window.addch(ch, curses.color_pair(color) | (curses.A_BOLD if bold else 0) | attributes)
parse_color = False parse_color = False
elif ch == '^': elif ch == '^':
parse_color = True parse_color = True
else: else:
window.addch(ch, curses.color_pair(color) | attributes) window.addch(ch, curses.color_pair(color) | (curses.A_BOLD if bold else 0) | attributes)
window.refresh() window.refresh()
class UIManager: class UIManager:
"""Manages curses windows and display""" """Manages curses windows and display"""
@ -196,7 +198,7 @@ class UIManager:
# Line 1: Hostname # Line 1: Hostname
hostname = server_info.hostname hostname = server_info.hostname
print_colored(self.info_window, f"^6═══ {hostname} ^6═══^7\n", 0) print_colored(self.info_window, f"^6═══^0 {hostname} ^7^6═══^7\n", 0)
# Line 2: Game info # Line 2: Game info
gametype = server_info.gametype gametype = server_info.gametype
@ -209,14 +211,14 @@ class UIManager:
maxclients = server_info.maxclients maxclients = server_info.maxclients
print_colored(self.info_window, print_colored(self.info_window,
f"^3Type:^7 {gametype} ^3Map:^7 {mapname} ^3Players:^7 {curclients}/{maxclients} " f"^3Type:^7^0 {gametype} ^7^3Map:^7^0 {mapname} ^7^3Players:^7^0 {curclients}/{maxclients} "
f"^3Limits (T/F/R/C):^7 {timelimit}/{fraglimit}/{roundlimit}/{caplimit}\n", 0) f"^7^3Limits (T/F/R/C):^7 {timelimit}/{fraglimit}/{roundlimit}/{caplimit}\n", 0)
# Line 3: Team headers and player lists # Line 3: Team headers and player lists
teams = game_state.player_tracker.get_players_by_team() teams = game_state.player_tracker.get_players_by_team()
if server_info.gametype in TEAM_MODES: if server_info.gametype in TEAM_MODES:
print_colored(self.info_window, f"^1(RED) ^4(BLUE) ^3(SPEC)\n", 0) print_colored(self.info_window, f"^0^1(RED) ^4(BLUE) ^3(SPEC)\n", 0)
red_players = teams['RED'][:4] red_players = teams['RED'][:4]
blue_players = teams['BLUE'][:4] blue_players = teams['BLUE'][:4]
@ -240,7 +242,7 @@ class UIManager:
line = f"{red}{' ' * red_pad}{blue}{' ' * blue_pad}{spec}\n" line = f"{red}{' ' * red_pad}{blue}{' ' * blue_pad}{spec}\n"
print_colored(self.info_window, line, 0) print_colored(self.info_window, line, 0)
else: else:
print_colored(self.info_window, f"^3(FREE)\n", 0) print_colored(self.info_window, f"^0^3(FREE)\n", 0)
free_players = teams['FREE'][:4] free_players = teams['FREE'][:4]
for player in free_players: for player in free_players:
print_colored(self.info_window, f"{player}\n", 0) print_colored(self.info_window, f"{player}\n", 0)