Compare commits
6 Commits
a03b4b0b93
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 530c12d9c0 | |||
| 993ed4f051 | |||
| 42b958acdb | |||
| f1ed9c3d2c | |||
| 4ac9432db9 | |||
| fbaa4564e4 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ venv3/
|
|||||||
cvarlist.txt
|
cvarlist.txt
|
||||||
curztest.py
|
curztest.py
|
||||||
rconpw.txt
|
rconpw.txt
|
||||||
|
CLAUDE*.md
|
||||||
|
|||||||
9
main.py
9
main.py
@ -367,6 +367,9 @@ def main_loop(screen):
|
|||||||
# Shutdown flag
|
# Shutdown flag
|
||||||
shutdown = False
|
shutdown = False
|
||||||
|
|
||||||
|
# Timer refresh tracking (update UI once per second)
|
||||||
|
last_ui_update = 0
|
||||||
|
|
||||||
# Setup JSON logging if requested
|
# Setup JSON logging if requested
|
||||||
json_logger = None
|
json_logger = None
|
||||||
if args.json_log:
|
if args.json_log:
|
||||||
@ -525,6 +528,12 @@ def main_loop(screen):
|
|||||||
formatted_msg, attributes = format_message(message)
|
formatted_msg, attributes = format_message(message)
|
||||||
ui.print_message(formatted_msg)
|
ui.print_message(formatted_msg)
|
||||||
|
|
||||||
|
# Update server info panel every second (for live timer display)
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_ui_update >= 1.0:
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
last_ui_update = current_time
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Clean up resources
|
# Clean up resources
|
||||||
logger.info("Shutting down...")
|
logger.info("Shutting down...")
|
||||||
|
|||||||
234
test_ui.py
Executable file
234
test_ui.py
Executable file
@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test mode for QLPyCon - Simulates game events to test UI
|
||||||
|
Run with: python3 test_ui.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import curses
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import queue
|
||||||
|
import threading
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from config import VERSION
|
||||||
|
from state import GameState
|
||||||
|
from parser import EventParser
|
||||||
|
from ui import UIManager
|
||||||
|
|
||||||
|
logger = logging.getLogger('test_ui')
|
||||||
|
|
||||||
|
def simulate_game_events(game_state, parser, ui):
|
||||||
|
"""Simulate game events for testing"""
|
||||||
|
|
||||||
|
# Wait for UI to initialize
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
ui.print_message("=== TEST MODE - SIMULATING GAME EVENTS ===\n")
|
||||||
|
ui.print_message(f"QLPyCon {VERSION} Test Mode\n\n")
|
||||||
|
|
||||||
|
# Event 1: Match start
|
||||||
|
time.sleep(2)
|
||||||
|
event = {
|
||||||
|
"TYPE": "MATCH_STARTED",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": 0,
|
||||||
|
"PLAYERS": [
|
||||||
|
{"NAME": "^1RedWarrior"},
|
||||||
|
{"NAME": "^4BlueSniper"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
# Event 2: Players join teams
|
||||||
|
time.sleep(2)
|
||||||
|
event = {
|
||||||
|
"TYPE": "PLAYER_SWITCHTEAM",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": 5,
|
||||||
|
"KILLER": {
|
||||||
|
"NAME": "^1RedWarrior",
|
||||||
|
"TEAM": "RED",
|
||||||
|
"OLD_TEAM": "SPECTATOR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
event = {
|
||||||
|
"TYPE": "PLAYER_SWITCHTEAM",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": 6,
|
||||||
|
"KILLER": {
|
||||||
|
"NAME": "^4BlueSniper",
|
||||||
|
"TEAM": "BLUE",
|
||||||
|
"OLD_TEAM": "SPECTATOR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
# Event 3: Some kills
|
||||||
|
for i in range(10):
|
||||||
|
time.sleep(3)
|
||||||
|
match_time = 10 + (i * 5)
|
||||||
|
|
||||||
|
event = {
|
||||||
|
"TYPE": "PLAYER_DEATH",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": match_time,
|
||||||
|
"KILLER": {
|
||||||
|
"NAME": "^1RedWarrior",
|
||||||
|
"TEAM": "RED",
|
||||||
|
"WEAPON": ["ROCKET", "RAILGUN", "LIGHTNING"][i % 3],
|
||||||
|
"HEALTH": 100 - (i * 10)
|
||||||
|
},
|
||||||
|
"VICTIM": {
|
||||||
|
"NAME": "^4BlueSniper",
|
||||||
|
"TEAM": "BLUE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
# Counter-kill
|
||||||
|
time.sleep(2)
|
||||||
|
event = {
|
||||||
|
"TYPE": "PLAYER_DEATH",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": match_time + 2,
|
||||||
|
"KILLER": {
|
||||||
|
"NAME": "^4BlueSniper",
|
||||||
|
"TEAM": "BLUE",
|
||||||
|
"WEAPON": ["PLASMA", "GRENADE", "SHOTGUN"][i % 3],
|
||||||
|
"HEALTH": 50
|
||||||
|
},
|
||||||
|
"VICTIM": {
|
||||||
|
"NAME": "^1RedWarrior",
|
||||||
|
"TEAM": "RED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
# Event 4: Chat messages
|
||||||
|
time.sleep(2)
|
||||||
|
ui.print_message("^1RedWarrior^7: ^2gg!\n")
|
||||||
|
time.sleep(1)
|
||||||
|
ui.print_message("^4BlueSniper^7: ^2nice shots\n")
|
||||||
|
|
||||||
|
# Event 5: Medal
|
||||||
|
time.sleep(2)
|
||||||
|
event = {
|
||||||
|
"TYPE": "PLAYER_MEDAL",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": 120,
|
||||||
|
"NAME": "^1RedWarrior",
|
||||||
|
"MEDAL": "EXCELLENT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
|
||||||
|
# Event 6: Match end
|
||||||
|
time.sleep(3)
|
||||||
|
event = {
|
||||||
|
"TYPE": "MATCH_REPORT",
|
||||||
|
"DATA": {
|
||||||
|
"TIME": 180,
|
||||||
|
"TSCORE0": "12",
|
||||||
|
"TSCORE1": "8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = parser.parse_event(json.dumps(event))
|
||||||
|
if result:
|
||||||
|
ui.print_message(result)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
ui.print_message("\n=== TEST COMPLETE ===\n")
|
||||||
|
ui.print_message("Press Ctrl-C twice to quit\n")
|
||||||
|
|
||||||
|
def test_main(screen):
|
||||||
|
"""Main test loop"""
|
||||||
|
|
||||||
|
# Initialize UI
|
||||||
|
ui = UIManager(screen, "tcp://127.0.0.1:27961 [TEST MODE]")
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
log_handler = ui.setup_logging()
|
||||||
|
logger.addHandler(log_handler)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Initialize game state
|
||||||
|
game_state = GameState()
|
||||||
|
|
||||||
|
# Set some initial server info
|
||||||
|
game_state.server_info.hostname = "^3Test Server [Demo Mode]"
|
||||||
|
game_state.server_info.map = "bloodrun"
|
||||||
|
game_state.server_info.gametype = "Team Deathmatch"
|
||||||
|
game_state.server_info.timelimit = "10"
|
||||||
|
game_state.server_info.fraglimit = "50"
|
||||||
|
game_state.server_info.maxclients = "16"
|
||||||
|
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
# Create parser
|
||||||
|
parser = EventParser(game_state)
|
||||||
|
|
||||||
|
# Display startup message
|
||||||
|
ui.print_message(f"*** QL pyCon Version {VERSION} - TEST MODE ***\n")
|
||||||
|
ui.print_message("This mode simulates game events to test the UI\n")
|
||||||
|
ui.print_message("Watch the timer tick in real-time!\n\n")
|
||||||
|
|
||||||
|
# Start event simulation in background thread
|
||||||
|
sim_thread = threading.Thread(
|
||||||
|
target=simulate_game_events,
|
||||||
|
args=(game_state, parser, ui),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
sim_thread.start()
|
||||||
|
|
||||||
|
# Setup input queue (even though we won't use it much)
|
||||||
|
input_queue = ui.setup_input_queue()
|
||||||
|
|
||||||
|
# Main loop - just keep updating UI
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Update server info display (this will show live timer)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
# Process any user input
|
||||||
|
while not input_queue.empty():
|
||||||
|
command = input_queue.get()
|
||||||
|
ui.print_message(f">>> {command}\n")
|
||||||
|
ui.print_message("[Test mode: commands not sent to server]\n")
|
||||||
|
|
||||||
|
time.sleep(0.1) # Update 10 times per second
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
curses.wrapper(test_main)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nTest mode exited.")
|
||||||
98
ui.py
98
ui.py
@ -46,7 +46,10 @@ def print_colored(window, message, attributes=0):
|
|||||||
^0 = reset, ^1 = red, ^2 = green, ^3 = yellow, ^4 = blue, ^5 = cyan, ^6 = magenta, ^7 = white, ^8 = bold, ^9 = underline
|
^0 = reset, ^1 = red, ^2 = green, ^3 = yellow, ^4 = blue, ^5 = cyan, ^6 = magenta, ^7 = white, ^8 = bold, ^9 = underline
|
||||||
"""
|
"""
|
||||||
if not curses.has_colors:
|
if not curses.has_colors:
|
||||||
|
try:
|
||||||
window.addstr(message)
|
window.addstr(message)
|
||||||
|
except curses.error:
|
||||||
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
color = 0
|
color = 0
|
||||||
@ -69,13 +72,19 @@ def print_colored(window, message, attributes=0):
|
|||||||
elif ord('1') <= val <= ord('6'):
|
elif ord('1') <= val <= ord('6'):
|
||||||
color = val - ord('0')
|
color = val - ord('0')
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
window.addch('^', curses.color_pair(color) | (curses.A_BOLD if bold else 0) | (curses.A_UNDERLINE if underline else 0) | attributes)
|
window.addch('^', curses.color_pair(color) | (curses.A_BOLD if bold else 0) | (curses.A_UNDERLINE if underline else 0) | attributes)
|
||||||
window.addch(ch, curses.color_pair(color) | (curses.A_BOLD if bold else 0) | (curses.A_UNDERLINE if underline else 0) | attributes)
|
window.addch(ch, curses.color_pair(color) | (curses.A_BOLD if bold else 0) | (curses.A_UNDERLINE if underline else 0) | attributes)
|
||||||
|
except curses.error:
|
||||||
|
return
|
||||||
parse_color = False
|
parse_color = False
|
||||||
elif ch == '^':
|
elif ch == '^':
|
||||||
parse_color = True
|
parse_color = True
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
window.addch(ch, curses.color_pair(color) | (curses.A_BOLD if bold else 0) | (curses.A_UNDERLINE if underline else 0) | attributes)
|
window.addch(ch, curses.color_pair(color) | (curses.A_BOLD if bold else 0) | (curses.A_UNDERLINE if underline else 0) | attributes)
|
||||||
|
except curses.error:
|
||||||
|
return
|
||||||
|
|
||||||
def update_autocomplete_display(window, current_input, first_word, words, ends_with_space):
|
def update_autocomplete_display(window, current_input, first_word, words, ends_with_space):
|
||||||
"""
|
"""
|
||||||
@ -200,6 +209,7 @@ class UIManager:
|
|||||||
self.input_queue = None
|
self.input_queue = None
|
||||||
self.command_history = []
|
self.command_history = []
|
||||||
self.history_index = -1
|
self.history_index = -1
|
||||||
|
self.cursor_pos = 0 # Track cursor position in input
|
||||||
|
|
||||||
self._init_curses()
|
self._init_curses()
|
||||||
self._create_windows()
|
self._create_windows()
|
||||||
@ -229,6 +239,10 @@ class UIManager:
|
|||||||
"""Create all UI windows"""
|
"""Create all UI windows"""
|
||||||
maxy, maxx = self.screen.getmaxyx()
|
maxy, maxx = self.screen.getmaxyx()
|
||||||
|
|
||||||
|
# Minimum terminal size check
|
||||||
|
if maxy < 20 or maxx < 80:
|
||||||
|
return False
|
||||||
|
|
||||||
# Server info window (top)
|
# Server info window (top)
|
||||||
self.info_window = curses.newwin(
|
self.info_window = curses.newwin(
|
||||||
INFO_WINDOW_HEIGHT,
|
INFO_WINDOW_HEIGHT,
|
||||||
@ -284,12 +298,60 @@ class UIManager:
|
|||||||
|
|
||||||
self.screen.noutrefresh()
|
self.screen.noutrefresh()
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def handle_resize(self):
|
||||||
|
"""Handle terminal resize event"""
|
||||||
|
try:
|
||||||
|
# Get new terminal dimensions
|
||||||
|
maxy, maxx = self.screen.getmaxyx()
|
||||||
|
|
||||||
|
# Minimum size check
|
||||||
|
if maxy < 20 or maxx < 80:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Update screen
|
||||||
|
curses.update_lines_cols()
|
||||||
|
self.screen.clear()
|
||||||
|
self.screen.addstr(0, 0, f"Quake Live PyCon: {self.host}")
|
||||||
|
self.screen.noutrefresh()
|
||||||
|
|
||||||
|
# Recreate windows with new dimensions
|
||||||
|
self.info_window.resize(INFO_WINDOW_HEIGHT, maxx - 4)
|
||||||
|
self.info_window.mvwin(INFO_WINDOW_Y, 2)
|
||||||
|
|
||||||
|
self.output_window.resize(maxy - 17, maxx - 4)
|
||||||
|
self.output_window.mvwin(OUTPUT_WINDOW_Y, 2)
|
||||||
|
|
||||||
|
self.divider_window.resize(1, maxx - 4)
|
||||||
|
self.divider_window.mvwin(maxy - 3, 2)
|
||||||
|
self.divider_window.clear()
|
||||||
|
self.divider_window.hline(curses.ACS_HLINE, maxx - 4)
|
||||||
|
|
||||||
|
self.input_window.resize(INPUT_WINDOW_HEIGHT, maxx - 6)
|
||||||
|
self.input_window.mvwin(maxy - 2, 4)
|
||||||
|
|
||||||
|
self.screen.addstr(maxy - 2, 2, '$ ')
|
||||||
|
|
||||||
|
# Refresh all windows
|
||||||
|
self.info_window.noutrefresh()
|
||||||
|
self.output_window.noutrefresh()
|
||||||
|
self.divider_window.noutrefresh()
|
||||||
|
self.input_window.noutrefresh()
|
||||||
|
self.screen.noutrefresh()
|
||||||
|
curses.doupdate()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except curses.error:
|
||||||
|
return False
|
||||||
|
|
||||||
def setup_input_queue(self):
|
def setup_input_queue(self):
|
||||||
"""Setup threaded input queue with command history and autocomplete"""
|
"""Setup threaded input queue with command history and autocomplete"""
|
||||||
def wait_stdin(q, window, manager):
|
def wait_stdin(q, window, manager):
|
||||||
current_input = ""
|
current_input = ""
|
||||||
cursor_pos = 0
|
cursor_pos = 0
|
||||||
|
manager.cursor_pos = 0 # Keep manager in sync
|
||||||
temp_history_index = -1
|
temp_history_index = -1
|
||||||
temp_input = "" # Temp storage when navigating history
|
temp_input = "" # Temp storage when navigating history
|
||||||
quit_confirm = False
|
quit_confirm = False
|
||||||
@ -306,6 +368,17 @@ class UIManager:
|
|||||||
if key == -1: # No input
|
if key == -1: # No input
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Handle terminal resize
|
||||||
|
if key == curses.KEY_RESIZE:
|
||||||
|
manager.handle_resize()
|
||||||
|
# Redraw input
|
||||||
|
window.erase()
|
||||||
|
window.addstr(0, 0, current_input)
|
||||||
|
window.move(0, cursor_pos)
|
||||||
|
window.noutrefresh()
|
||||||
|
curses.doupdate()
|
||||||
|
continue
|
||||||
|
|
||||||
# Tab key - cycle through suggestions
|
# Tab key - cycle through suggestions
|
||||||
if key == ord('\t') or key == 9:
|
if key == ord('\t') or key == 9:
|
||||||
if suggestions:
|
if suggestions:
|
||||||
@ -325,6 +398,7 @@ class UIManager:
|
|||||||
words[-1] = suggestions[suggestion_index]
|
words[-1] = suggestions[suggestion_index]
|
||||||
current_input = ' '.join(words)
|
current_input = ' '.join(words)
|
||||||
cursor_pos = len(current_input)
|
cursor_pos = len(current_input)
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
window.erase()
|
window.erase()
|
||||||
@ -387,6 +461,7 @@ class UIManager:
|
|||||||
q.put(current_input)
|
q.put(current_input)
|
||||||
current_input = ""
|
current_input = ""
|
||||||
cursor_pos = 0
|
cursor_pos = 0
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
temp_history_index = -1
|
temp_history_index = -1
|
||||||
temp_input = ""
|
temp_input = ""
|
||||||
suggestions = []
|
suggestions = []
|
||||||
@ -407,6 +482,7 @@ class UIManager:
|
|||||||
temp_history_index -= 1
|
temp_history_index -= 1
|
||||||
current_input = manager.command_history[temp_history_index]
|
current_input = manager.command_history[temp_history_index]
|
||||||
cursor_pos = len(current_input)
|
cursor_pos = len(current_input)
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
suggestions = []
|
suggestions = []
|
||||||
suggestion_index = -1
|
suggestion_index = -1
|
||||||
original_word = ""
|
original_word = ""
|
||||||
@ -427,6 +503,7 @@ class UIManager:
|
|||||||
current_input = manager.command_history[temp_history_index]
|
current_input = manager.command_history[temp_history_index]
|
||||||
|
|
||||||
cursor_pos = len(current_input)
|
cursor_pos = len(current_input)
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
suggestions = []
|
suggestions = []
|
||||||
suggestion_index = -1
|
suggestion_index = -1
|
||||||
original_word = ""
|
original_word = ""
|
||||||
@ -438,6 +515,7 @@ class UIManager:
|
|||||||
elif key == curses.KEY_LEFT:
|
elif key == curses.KEY_LEFT:
|
||||||
if cursor_pos > 0:
|
if cursor_pos > 0:
|
||||||
cursor_pos -= 1
|
cursor_pos -= 1
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
window.move(0, cursor_pos)
|
window.move(0, cursor_pos)
|
||||||
window.noutrefresh()
|
window.noutrefresh()
|
||||||
|
|
||||||
@ -445,6 +523,7 @@ class UIManager:
|
|||||||
elif key == curses.KEY_RIGHT:
|
elif key == curses.KEY_RIGHT:
|
||||||
if cursor_pos < len(current_input):
|
if cursor_pos < len(current_input):
|
||||||
cursor_pos += 1
|
cursor_pos += 1
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
window.move(0, cursor_pos)
|
window.move(0, cursor_pos)
|
||||||
window.noutrefresh()
|
window.noutrefresh()
|
||||||
|
|
||||||
@ -453,6 +532,7 @@ class UIManager:
|
|||||||
if cursor_pos > 0:
|
if cursor_pos > 0:
|
||||||
current_input = current_input[:cursor_pos-1] + current_input[cursor_pos:]
|
current_input = current_input[:cursor_pos-1] + current_input[cursor_pos:]
|
||||||
cursor_pos -= 1
|
cursor_pos -= 1
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
temp_history_index = -1 # Exit history mode
|
temp_history_index = -1 # Exit history mode
|
||||||
|
|
||||||
window.erase()
|
window.erase()
|
||||||
@ -477,6 +557,7 @@ class UIManager:
|
|||||||
char = chr(key)
|
char = chr(key)
|
||||||
current_input = current_input[:cursor_pos] + char + current_input[cursor_pos:]
|
current_input = current_input[:cursor_pos] + char + current_input[cursor_pos:]
|
||||||
cursor_pos += 1
|
cursor_pos += 1
|
||||||
|
manager.cursor_pos = cursor_pos
|
||||||
temp_history_index = -1 # Exit history mode
|
temp_history_index = -1 # Exit history mode
|
||||||
|
|
||||||
window.erase()
|
window.erase()
|
||||||
@ -521,7 +602,9 @@ class UIManager:
|
|||||||
"""Print formatted message to output window"""
|
"""Print formatted message to output window"""
|
||||||
print_colored(self.output_window, message, attributes)
|
print_colored(self.output_window, message, attributes)
|
||||||
self.output_window.noutrefresh()
|
self.output_window.noutrefresh()
|
||||||
self.input_window.move(0, 0)
|
# Restore cursor to input window at current position
|
||||||
|
self.input_window.move(0, self.cursor_pos)
|
||||||
|
self.input_window.noutrefresh()
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
def update_server_info(self, game_state):
|
def update_server_info(self, game_state):
|
||||||
@ -579,7 +662,10 @@ class UIManager:
|
|||||||
f"{limit_display}^0\n", 0)
|
f"{limit_display}^0\n", 0)
|
||||||
|
|
||||||
# Blank lines to fill
|
# Blank lines to fill
|
||||||
|
try:
|
||||||
self.info_window.addstr("\n")
|
self.info_window.addstr("\n")
|
||||||
|
except curses.error:
|
||||||
|
pass
|
||||||
|
|
||||||
# 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()
|
||||||
@ -694,7 +780,10 @@ class UIManager:
|
|||||||
print_colored(self.info_window, line, 0)
|
print_colored(self.info_window, line, 0)
|
||||||
|
|
||||||
# Blank lines to fill
|
# Blank lines to fill
|
||||||
|
try:
|
||||||
self.info_window.addstr("\n")
|
self.info_window.addstr("\n")
|
||||||
|
except curses.error:
|
||||||
|
pass
|
||||||
|
|
||||||
# List spectators on one line
|
# List spectators on one line
|
||||||
spec_list = " ".join(spec_players)
|
spec_list = " ".join(spec_players)
|
||||||
@ -702,12 +791,17 @@ class UIManager:
|
|||||||
print_colored(self.info_window, line, 0)
|
print_colored(self.info_window, line, 0)
|
||||||
|
|
||||||
# Blank lines to fill
|
# Blank lines to fill
|
||||||
|
try:
|
||||||
self.info_window.addstr("\n")
|
self.info_window.addstr("\n")
|
||||||
|
except curses.error:
|
||||||
|
pass
|
||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
separator = "^7" + "═" * (max_x - 1) + "^7"
|
separator = "^7" + "═" * (max_x - 1) + "^7"
|
||||||
print_colored(self.info_window, separator, 0)
|
print_colored(self.info_window, separator, 0)
|
||||||
|
|
||||||
self.info_window.noutrefresh()
|
self.info_window.noutrefresh()
|
||||||
|
# Restore cursor to input window at current position
|
||||||
|
self.input_window.move(0, self.cursor_pos)
|
||||||
|
self.input_window.noutrefresh()
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|||||||
Reference in New Issue
Block a user