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:
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Message formatting and colorization for QLPyCon
|
||||
Handles Quake color codes and team prefixes
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
from .constants import TEAM_COLORS, COLOR_CODE_PATTERN, SPECIAL_CHAR
|
||||
|
||||
|
||||
def strip_color_codes(text):
|
||||
"""Remove Quake color codes (^N) from text"""
|
||||
return COLOR_CODE_PATTERN.sub('', text)
|
||||
|
||||
|
||||
def get_team_prefix(player_name, player_tracker):
|
||||
"""Get color-coded team prefix for a player"""
|
||||
if not player_tracker.server_info.is_team_mode():
|
||||
return ''
|
||||
|
||||
team = player_tracker.get_team(player_name)
|
||||
if not team:
|
||||
return ''
|
||||
|
||||
return TEAM_COLORS.get(team, '')
|
||||
|
||||
|
||||
def should_add_timestamp(message):
|
||||
"""Determine if a message should get a timestamp"""
|
||||
# Skip status command output
|
||||
skip_keywords = ['map:', 'num score', '---', 'bot', 'status']
|
||||
if any(kw in message for kw in skip_keywords):
|
||||
# But allow "zmq RCON" lines (command echoes)
|
||||
if 'zmq RCON' not in message:
|
||||
return False
|
||||
|
||||
# Skip very short messages or fragments
|
||||
stripped = message.strip()
|
||||
if len(stripped) <= 2:
|
||||
return False
|
||||
|
||||
# Skip messages with leading spaces (status fragments)
|
||||
if message.startswith(' ') and len(stripped) < 50:
|
||||
return False
|
||||
|
||||
# Skip pure numbers
|
||||
if stripped.isdigit():
|
||||
return False
|
||||
|
||||
# Skip short single words
|
||||
if len(stripped) < 20 and ' ' not in stripped:
|
||||
return False
|
||||
|
||||
# Skip IP addresses (xxx.xxx.xxx.xxx or xxx.xxx.xxx.xxx:port)
|
||||
allowed_chars = set('0123456789.:')
|
||||
if stripped.count('.') == 3 and all(c in allowed_chars for c in stripped):
|
||||
return False
|
||||
|
||||
# Skip messages starting with ***
|
||||
if message.startswith('***'):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def format_message(message, add_timestamp=True):
|
||||
"""
|
||||
Format a message for display
|
||||
- Strips special characters
|
||||
- Adds timestamp if appropriate
|
||||
- Handles broadcast formatting
|
||||
"""
|
||||
# Clean up message
|
||||
message = message.replace("\\n", "")
|
||||
message = message.replace(SPECIAL_CHAR, "")
|
||||
|
||||
# Handle broadcast messages
|
||||
attributes = 0
|
||||
if message[:10] == "broadcast:":
|
||||
message = message[11:]
|
||||
attributes = 1 # Bold
|
||||
|
||||
# Handle print messages
|
||||
if message[:7] == "print \"":
|
||||
message = message[7:-2] + "\n"
|
||||
|
||||
# Add timestamp if requested and appropriate
|
||||
if add_timestamp and should_add_timestamp(message):
|
||||
timestamp = time.strftime('%H:%M:%S')
|
||||
message = f"^3[^7{timestamp}^3]^7 {message}"
|
||||
|
||||
return message, attributes
|
||||
|
||||
|
||||
def format_chat_message(message, player_tracker):
|
||||
"""
|
||||
Format chat messages with team prefixes and colors
|
||||
Handles both regular chat (Name: msg) and team chat ((Name): msg or (Name) (Location): msg)
|
||||
"""
|
||||
# Strip special character
|
||||
clean_msg = message.replace(SPECIAL_CHAR, '')
|
||||
|
||||
# Team chat with location: (PlayerName) (Location): message
|
||||
# Location can have nested parens like (Lower Floor (Near Yellow Armour))
|
||||
if clean_msg.strip().startswith('(') and ')' in clean_msg:
|
||||
# Extract player name (first parenthetical)
|
||||
player_match = re.match(r'^(\([^)]+\))', clean_msg)
|
||||
if not player_match:
|
||||
return message
|
||||
|
||||
player_part = player_match.group(1)
|
||||
rest = clean_msg[len(player_part):].lstrip()
|
||||
|
||||
# Check for location (another parenthetical)
|
||||
if rest.startswith('('):
|
||||
# Count parens to handle nesting
|
||||
paren_count = 0
|
||||
location_end = -1
|
||||
for i, char in enumerate(rest):
|
||||
if char == '(':
|
||||
paren_count += 1
|
||||
elif char == ')':
|
||||
paren_count -= 1
|
||||
if paren_count == 0:
|
||||
location_end = i + 1
|
||||
break
|
||||
|
||||
# Check if location ends with colon
|
||||
if location_end > 0 and location_end < len(rest) and rest[location_end] == ':':
|
||||
location_part = rest[:location_end]
|
||||
message_part = rest[location_end + 1:]
|
||||
|
||||
# Get team prefix
|
||||
name_match = re.match(r'\(([^)]+)\)', player_part)
|
||||
if name_match:
|
||||
player_name = strip_color_codes(name_match.group(1).strip())
|
||||
team_prefix = get_team_prefix(player_name, player_tracker)
|
||||
location_clean = strip_color_codes(location_part)
|
||||
return f"^8^5[TEAMSAY]^7^0 {team_prefix}^8{player_part}^0^3{location_clean}^7:^5{message_part}"
|
||||
|
||||
# Team chat without location: (PlayerName): message
|
||||
colon_match = re.match(r'^(\([^)]+\)):(\s*.*)', clean_msg)
|
||||
if colon_match:
|
||||
player_part = colon_match.group(1)
|
||||
message_part = colon_match.group(2)
|
||||
|
||||
name_match = re.match(r'\(([^)]+)\)', player_part)
|
||||
if name_match:
|
||||
player_name = strip_color_codes(name_match.group(1).strip())
|
||||
team_prefix = get_team_prefix(player_name, player_tracker)
|
||||
return f"^8^5[TEAMSAY]^7^0 {team_prefix}^8{player_part}^7^0:^5{message_part}\n"
|
||||
|
||||
# Regular chat: PlayerName: message
|
||||
parts = clean_msg.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
player_name = strip_color_codes(parts[0].strip())
|
||||
team_prefix = get_team_prefix(player_name, player_tracker)
|
||||
|
||||
# Preserve original color-coded name
|
||||
original_parts = message.replace(SPECIAL_CHAR, '').split(':', 1)
|
||||
if len(original_parts) == 2:
|
||||
return f"^8^2[SAY]^7^0 {team_prefix}^8{original_parts[0]}^0^7:^2{original_parts[1]}"
|
||||
|
||||
return message
|
||||
|
||||
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 .constants import POWERUP_COLORS
|
||||
import time
|
||||
|
||||
if message.startswith("broadcast:"):
|
||||
message = message[11:].strip()
|
||||
|
||||
# Strip print " wrapper
|
||||
if message.startswith('print "'):
|
||||
message = message[7:]
|
||||
if message.endswith('"'):
|
||||
message = message[:-1]
|
||||
message = message.strip()
|
||||
|
||||
# Powerup pickup: "PlayerName got the PowerupName!"
|
||||
pickup_match = re.match(r'^(.+?)\s+got the\s+(.+?)!', message)
|
||||
if pickup_match:
|
||||
player_name = pickup_match.group(1).strip()
|
||||
powerup_name = pickup_match.group(2).strip()
|
||||
|
||||
player_clean = strip_color_codes(player_name)
|
||||
team_prefix = get_team_prefix(player_clean, player_tracker)
|
||||
|
||||
colored_powerup = POWERUP_COLORS.get(powerup_name, f'^6{powerup_name}^7')
|
||||
timestamp = time.strftime('%H:%M:%S')
|
||||
return f"^3[^7{timestamp}^3] ^8^5[POWERUP]^7^0 {team_prefix}^8{player_name}^0 ^7got the {colored_powerup}!\n"
|
||||
|
||||
# Powerup carrier kill: "PlayerName killed the PowerupName carrier!"
|
||||
carrier_match = re.match(r'^(.+?)\s+killed the\s+(.+?)\s+carrier!', message)
|
||||
if carrier_match:
|
||||
player_name = carrier_match.group(1).strip()
|
||||
powerup_name = carrier_match.group(2).strip()
|
||||
|
||||
player_clean = strip_color_codes(player_name)
|
||||
team_prefix = get_team_prefix(player_clean, player_tracker)
|
||||
|
||||
colored_powerup = POWERUP_COLORS.get(powerup_name, f'^6{powerup_name}^7')
|
||||
timestamp = time.strftime('%H:%M:%S')
|
||||
return f"^3[^7{timestamp}^3] ^8^5[POWERUP]^7^0 {team_prefix}^8{player_name}^0 ^7killed the {colored_powerup} ^7carrier!\n"
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user