1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
"""CS2 recording configuration generation."""
from dataclasses import dataclass
from pathlib import Path
@dataclass
class RecordingConfig:
"""Configuration for recording a demo POV."""
demo_name: str
player_index: int
player_name: str = ""
player_steamid: int = 0
resolution: tuple[int, int] = (1920, 1080)
hide_hud: bool = True
spec_mode: int = 4 # 4 = first-person, 5 = third-person, 6 = free roam
@property
def player_account_id(self) -> int:
"""Convert SteamID64 to account ID (for spec_player_by_accountid)."""
if self.player_steamid > 76561197960265728:
return self.player_steamid - 76561197960265728
return 0
# CFG template for recording
# Note: playdemo is asynchronous - commands after it execute before demo loads
# We use binds and aliases so user can lock to player once demo is ready
BASE_CFG_TEMPLATE = """\
// CS2 POV Recording Configuration
// Generated by cs2pov
// Target player: {player_name}
// Disable unnecessary visual elements
spec_show_xray 0
cl_show_observer_crosshair 0
// HUD settings
{hud_commands}
// Set spectator mode (4 = first-person POV)
spec_mode {spec_mode}
// Auto-quit when demo ends
demo_quitafterplayback 1
// Create alias to lock to player by name (more reliable than index)
alias "lock_pov" "spec_player \\"{player_name}\\""
// Bind F5 to lock to player (automation will press this)
bind "F5" "lock_pov"
// Also try binding to a mouse button for manual use
bind "MOUSE4" "lock_pov"
// Load and play the demo from replays directory
playdemo "replays/{demo_name}"
// These run before demo loads but we try anyway
spec_player "{player_name}"
spec_mode {spec_mode}
// Tell user what's happening
echo "=========================================="
echo "CS2POV: Recording {player_name}"
echo "CS2POV: Automation will keep view locked"
echo "=========================================="
"""
# Additional commands to hide HUD elements (CS2 compatible)
HUD_HIDE_COMMANDS = """\
cl_draw_only_deathnotices 1
cl_drawhud 0\
"""
HUD_SHOW_COMMANDS = """\
cl_draw_only_deathnotices 0
cl_drawhud 1\
"""
def generate_recording_cfg(config: RecordingConfig, output_path: Path) -> Path:
"""Generate a CS2 recording configuration file.
Args:
config: Recording configuration
output_path: Path to write the CFG file
Returns:
Path to the generated CFG file
"""
hud_commands = HUD_HIDE_COMMANDS if config.hide_hud else HUD_SHOW_COMMANDS
cfg_content = BASE_CFG_TEMPLATE.format(
hud_commands=hud_commands,
spec_mode=config.spec_mode,
player_name=config.player_name,
demo_name=config.demo_name,
)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(cfg_content)
return output_path
def generate_spec_player_cfg(player_index: int, output_path: Path) -> Path:
"""Generate a separate CFG to lock spectator to a specific player.
This is called after the demo starts loading to ensure the player
entities exist before trying to spectate them.
Args:
player_index: Player index to spectate (0-based from our player list)
output_path: Path to write the CFG file
Returns:
Path to the generated CFG file
"""
# In CS2 demos, spec_player takes the entity index
# Players typically have entity indices starting around 1-10
# We'll try the player index + 1 as a starting point
entity_index = player_index + 1
cfg_content = f"""\
// Lock to player
spec_player {entity_index}
spec_mode 4
"""
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(cfg_content)
return output_path
|