Commit graph

37 commits

Author SHA1 Message Date
76538c3216 Block team changes during demo playback
Prevent spectators from joining a playing team during demo
playback, which would disrupt the playback state. Prints a
message to the client instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:26:28 +08:00
300e7c431a Pause demo playback when no spectators are watching
svdemo_pauseEmpty (default: 1) pauses frame processing when no
client is CS_ACTIVE during demo playback. Prevents the demo from
advancing with nobody watching. Time residual is cleared so the
demo resumes from where it paused when a spectator connects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:25:16 +08:00
b79eb77bb3 Remove spectatorClientNum hack
spectatorClientNum was hardcoded to MAX_CLIENTS-1 but the spectator
actually connected at a different slot. All skip checks using it
were no-ops protecting the wrong slot. Removed entirely:

- Zombie slots handle player slot reservation
- G_RunFrame recreates the spectator entity via ClientThink_real
- PlayerState injection writes all slots (spectator's ps gets
  overwritten by ClientThink_real anyway)
- SVD_SpectatorClientNum accessor removed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:20:52 +08:00
4d89d35a6d Send gamestate resend on map_restart (matches real server behavior)
A real map_restart sends SV_SendClientGameState to all clients,
which triggers CL_ParseGamestate → CL_ClearState on the client,
wiping all snapshot and entity history. Previously we only forced
non-delta snapshots which doesn't clear cgame's entity state.

Now we call SV_SendClientGameState for all active clients on the
restart frame, exactly matching real server behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:15:31 +08:00
595cf9864b Reset playback delta state on map_restart marker
The recording resets its delta state on map_restart (writes from
zero baseline). The playback must do the same, otherwise the
delta decoder uses stale pre-restart state as baseline, producing
corrupt entity data and preventing EF_TELEPORT_BIT from being
decoded correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:11:08 +08:00
5a18130ba0 Force non-delta snapshot on map_restart during playback
snapFlagServerBit toggle alone doesn't clear client entity
interpolation state. Also reset deltaMessage=-1 for all active
clients, forcing the next snapshot to be full (non-delta). The
client receives deltaNum<=0, clears old entity state, and
renders all entities at their new positions without interpolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:08:34 +08:00
479443513a Toggle snapFlagServerBit on map_restart during demo playback
Record a per-frame restart flag (1 byte) set by SVD_ResetDeltaState
when map_restart occurs. During playback, toggle svs.snapFlagServerBit
so the client treats the next snapshot as a fresh baseline — no
interpolation of entities from pre-restart positions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:03:45 +08:00
cc081ddee4 Fix chat capture: capture any target, deduplicate in buffer
Per-client chat is sent to every connected client in a loop.
Capture chat/tchat commands regardless of target clientNum, and
deduplicate by checking if the exact string is already buffered
for the current frame.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:55:19 +08:00
90b26dc2a1 Capture per-client chat commands for demo recording
Chat messages use per-client trap_SendServerCommand (not broadcast),
so they weren't captured. Move capture hook to SV_GameSendServerCommand
which is the game module boundary. Capture broadcasts (clientNum=-1)
and chat/tchat commands sent to client 0 (first in the per-client
loop, deduplicated by only capturing once).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:53:53 +08:00
8b7ec11034 Fix server command replay: broadcast instead of targeting slot 63
spectatorClientNum was hardcoded to MAX_CLIENTS-1 (63) but the
spectator actually connects at the first free slot after zombie
reservations. Broadcasting with SV_SendServerCommand(NULL, ...)
reaches the spectator regardless of their actual slot number.
Zombie clients (< CS_PRIMED) are skipped by the broadcast loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:50:15 +08:00
0d1f1d515e Restore svs.time sync for trajectory interpolation
Reverting the svs.time change from the bugfix commit — entity
trajectories (rockets, grenades, bobbing items) need svs.time to
match recorded time for client-side interpolation. The zombie
timeout issue is already handled by skipping SV_CheckTimeouts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:47:51 +08:00
4f0d46024b Record and replay broadcast server commands (chat, prints)
Capture broadcast server commands (chat, print, cp, etc.) from
SV_SendServerCommand when cl==NULL. Buffer up to 64 commands per
frame. Written after configstrings in the demo file, replayed to
the spectator client during playback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:41:18 +08:00
330cc30ae7 Optional LZ4 compression for demo files
Per-frame entity and playerState blocks are compressed with LZ4
when svdemo_compress is set (default: 1). The block format writes
[original_size][compressed_size][data] — compressed_size=0 means
uncompressed. Playback auto-detects based on header flags.

Demo format bumped to version 2 with SVDEMO_FLAG_COMPRESSED flag.
Version 1 (uncompressed) demos are no longer compatible.

Uses the lz4.c/lz4.h library already in the server code directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:22:41 +08:00
e58414f564 Auto-record demos with svdemo_autorecord cvar
Set svdemo_autorecord 1 to automatically record a demo on every
map load. Demo files are named <mapname>_YYYYMMDD_HHMMSS.svdm
in the svdemos/ directory.

Refactored SVD_Record_f to use SVD_StartRecording helper so both
manual and auto recording share the same code path. Also fixed
prevPlayers delta state not being cleared on recording start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:11:27 +08:00
6fc96e7070 Auto-stop demo recording on map change
SV_SpawnServer reinitializes the game module, invalidating all
entity and configstring state. Recording across a map boundary
would produce corrupt deltas. Stop recording automatically at
the start of SV_SpawnServer (before game shutdown).

map_restart is unaffected — it has its own path (SV_MapRestart_f)
which doesn't call SV_SpawnServer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:08:38 +08:00
5987109014 Filter CS_SERVERINFO/SYSTEMINFO in per-frame configstring changes
The per-frame configstring path in SVD_ReadFrame was not filtering
dangerous configstrings like SVD_ApplyConfigstrings does. When a
recorded map_restart changed CS_SERVERINFO (containing maxclients),
applying it overwrote maxclients=64 with the recorded value,
triggering a latched restart loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:06:27 +08:00
a1167ff398 Fix 8 netdemo bugs found in code review
1. File handle leak: SVD_Play_f opened file twice, first handle leaked.
   Fix: memset demo state before opening.

2. svdemo_stop now handles both recording and playback via SVD_Stop_f.
   Playback stop disconnects client to return to menu.

3. Zombie client timeout: skip SV_CheckTimeouts during playback so
   reserved player slots aren't freed.

4. Buffer overflow: increase entity buffer to MAX_GENTITIES*300 and
   playerState buffer to MAX_CLIENTS*600 for worst-case first frame.
   Made static to avoid stack overflow.

5. svs.time jump: don't overwrite svs.time with recorded time.
   Server time advances normally, avoiding timeout/heartbeat issues.

6. map_restart: SVD_ResetDeltaState clears entity/player delta state
   so next frame writes full states, preventing corrupt deltas.

7. Demo end and manual stop both disconnect the client.

8. SV_Shutdown cleans up active recording/playback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:03:33 +08:00
b48a0575f1 Proper demo playback cleanup and state reset
- Extract SVD_CleanupPlayback for shared cleanup logic
- Called on demo end (SVD_PlaybackFrame) and manual stop
- Frees zombie client slots, saved configstrings, file handle
- Clears sv_demoplaying cvar so game module exits demo mode
- Hook into SV_Shutdown to clean up on server shutdown/map change
- Allows playing another demo after one finishes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:55:12 +08:00
0bd116982a Add player follow mode during demo playback
Call CalculateRanks and ClientEndFrame for the spectator in demo
mode G_RunFrame. SpectatorClientEndFrame copies the followed
player's recorded playerState into the spectator's ps, giving
first-person view with full HUD (health, armor, ammo, weapon).

Standard spectator controls work: mouse1 to cycle, mouse2 to
enter follow from free camera.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:52:58 +08:00
1a186eeb81 Fix player disconnect handling during demo playback
Clear the game module's playerState when a recorded player
disconnects, so G_RunFrame sees commandTime=0 and marks them
as CON_DISCONNECTED. Without this, disconnected players stayed
visible on the scoreboard indefinitely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:48:35 +08:00
1f8c8aea1c Record playerStates for scoreboard and future follow mode
Record delta-compressed playerState_t for each active player per
frame using MSG_WriteDeltaPlayerstate. During playback, inject
into game module via SV_GameClientNum and mark players as
CON_CONNECTED with correct team. CalculateRanks and the
scoreboard now show recorded players with scores.

This also lays the groundwork for player-follow spectating.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:46:40 +08:00
de9863da57 Delta-compress netdemo entity states
Use MSG_WriteDeltaEntity/MSG_ReadDeltaEntity for entity state
serialization. Only changed fields are written per frame. PVS
fields (svFlags, linked, origin, absmin, absmax) also use a
1-bit change flag to skip when unchanged.

Reduces a 10-second demo from ~1400KB to ~52KB (27x smaller).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:36:02 +08:00
a8044aad8b Server-side demo recording and playback (netdemo)
Records full entity state array each server frame, enabling
free-camera demo playback from any viewpoint.

Recording:
- svdemo_record <name> / svdemo_stop
- Captures entityState_t + PVS fields (svFlags, linked,
  currentOrigin, absmin, absmax) for all active entities
- Records configstring changes per frame
- File format: svdemos/<name>.svdm

Playback:
- svdemo_play <name>
- Loads map with maxclients=64, reserves recorded player slots
  (CS_ZOMBIE with safe rate/timing) so spectator gets a high slot
- Injects recorded entities into sv.gentities each frame with
  SV_LinkEntity for PVS visibility
- Re-applies demo configstrings (CS_PLAYERS etc.) after map load,
  skipping CS_SERVERINFO/CS_SYSTEMINFO to avoid latch restarts
- Game module runs in demo mode (sv_demoplaying cvar): G_RunFrame
  only processes spectator movement, skips all entity logic
- Spectator forced to TEAM_SPECTATOR on connect (ClientBegin)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:28:55 +08:00
ba1c1b5a60 QL step jump with master gate and projected validation
Add full QL-style PM_StepSlideMove with:
- Master gate: trace from start to final position filters micro-bumps
- Projected position validation: trace down at where player would be
  without collision, rejects walls (startsolid) and confirms stairs
- Air-step friction: 3% horizontal penalty on airborne step-ups
- PM_Jump() call when all gates pass on valid stair geometry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:58:11 +08:00
9b0bd3c03f Input sticky buttons fixed 2026-03-21 20:51:28 +08:00
043e3a2def QL stair traversal: conditional velocity clip + air steps
Two changes to PM_StepSlideMove that transform stair traversal:

1. Remove Q3 velocity[2]>0 gate (QL pm_airSteps): allow step-ups
   while airborne, enabling bunny-hop stair traversal.

2. Conditional velocity clip: only clip velocity to step-down surface
   when moving INTO it (dot product < 0). Skip clip when velocity is
   moving away (dot >= 0), preserving upward momentum through steps.
   This is THE key mechanic for smooth QL-style stair hopping.

Also adds 100ms jump cooldown (lastJumpTime) to prevent same-frame
double-fires during rapid step-ups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:08:16 +08:00
3327e9680c QL movement foundation: auto-hop, PM_Jump/PM_CanJump extraction, lastJumpTime
- Extract PM_Jump from PM_CheckJump for reuse by future step jump code
- Add PM_CanJump gate function (checks respawned, pm_type, upmove)
- Remove PMF_JUMP_HELD gate for QL-style auto-hop (hold jump to bunny hop)
- Add lastJumpTime to playerState_t (networked via msg.c) for jump cooldown
- bg_slidemove.c unchanged (no step jump yet -- needs proper QL analysis)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 05:45:18 +08:00
009dc313d4 Implement QL-style auto-hop (always enabled)
Remove the PMF_JUMP_HELD gate from PM_CheckJump so players can
hold jump to bunny hop continuously without releasing between
hops. The existing Pmove() outer loop already forces upmove=20
when PMF_JUMP_HELD is set, making this the only change needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 04:33:41 +08:00
0602b6ad4b Extract PM_Jump from PM_CheckJump (QL-style refactor)
Separate the jump execution (velocity, event, animation) from the
gate logic (respawn check, upmove threshold, jump-held check).
This prepares the code for QL features that need to trigger jumps
from contexts other than the normal ground jump path (step jump,
double jump, etc). No behavior change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 04:31:49 +08:00
Sergei Shubin
a82244334c Build system cleanup: rename to quake3live.exe, fix DLL loading, default to missionpack
- Rename output binary from quake3.exe to quake3live.exe across all configurations
- Add DLL_ONLY define to Release TA and Debug TA configs to force native DLL loading
  (VM_CallCompiled was a stub returning 0, causing "ui version 0" errors)
- Default fs_game to "missionpack" so the engine loads from workdir/missionpack/
- Add AfterBuild copy for ui.vcxproj to deploy uix86.dll to workdir
- Fix .gitignore: add Debug_TA/, remove *.exe/*.dll/*.lib/*.map patterns that were
  hiding real SDK tools in code/win32/mod-sdk-setup/bin/
- Remove q3_ui project from solution (not used in TA builds)
- Increase hunk/zone memory defaults for modern systems

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 18:17:28 +08:00
Sergei Shubin
5eb0118ee3 Copy game/cgame/ui to missionpack/ for TA configurations
Debug TA and Release TA configs now copy their output DLLs to
workdir/missionpack/ instead of workdir/baseq3/, matching the
Q3 Team Arena directory layout. MISSIONPACK was already defined
for TA configs; this completes the baseline TA setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 16:06:35 +08:00
Sergei Shubin
85fdc88c72 Force vm_game/vm_cgame/vm_ui to DLL-only (0) when DLL_ONLY is defined
Default was 2 (JIT/QVM), causing fallback to ui.qvm when DLL not found.
Use CVAR_INIT|CVAR_ROM to prevent config files from overriding back to QVM.
Mirrors the same pattern used for sv_pure in sv_init.c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 14:04:12 +08:00
Sergei Shubin
37f0bc95eb Define DLL_ONLY for all configurations; fix vm_x86.c guard
Add unconditional ItemDefinitionGroup in quake3.vcxproj to define DLL_ONLY
for all configurations, disabling QVM interpreter/JIT in favour of DLLs.

Fix vm_x86.c: VM_Compile was outside the #ifndef DLL_ONLY guard while
VM_CallCompiled was inside it, causing a duplicate symbol link error.
Extend the guard to cover VM_Compile as well.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 14:00:29 +08:00
Sergei Shubin
a1fce2ed93 Add AfterBuild Copy tasks to deploy outputs to workdir
game/cgame/q3_ui copy DLLs to workdir/baseq3/, quake3 copies exe to workdir/.
Uses MSBuild <Copy> task with per-config _CopyOutputFile property; Alpha AXP
configs are skipped (no _CopyOutputFile defined).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 13:53:13 +08:00
Sergei Shubin
111dce16ab Upgrade mouse input from DirectInput 3 to DirectInput 8
- Use DirectInput8Create() instead of dynamic dinput.dll loading
- Switch to LPDIRECTINPUT8 / LPDIRECTINPUTDEVICE8 types and DI8 macros
- Replace custom MYDATA data format with c_dfDIMouse2 (8-button support)
- Use DIMOUSESTATE2 in GetDeviceState (correct size, 8 buttons)
- Add MOUSE5 button mapping via DIMOFS_BUTTON4
- Link against dinput8.lib; define required GUIDs inline to avoid dxguid.lib conflicts
- Fix .gitignore: remove Win32/ pattern that was shadowing code/win32/ source dir

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 13:44:21 +08:00
Sergei Shubin
f85c79bf5a Migrate to Visual Studio 2022: replace .vcproj with .vcxproj, add .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 13:36:28 +08:00
Sergei Shubin
4c57221941 Initial commit: Quake 3 1.32b GPL source 2026-03-18 13:32:24 +08:00