Skip to content

Finish reloads at the last reload click - #2245

Merged
AdamTadeusz merged 2 commits into
NeotokyoRebuild:masterfrom
sunzenshen:reload-ready-at-cue
Oct 26, 2026
Merged

AdamTadeusz merged 2 commits into
NeotokyoRebuild:masterfrom
sunzenshen:reload-ready-at-cue

Conversation

@sunzenshen

@sunzenshen sunzenshen commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds a script field that defines when a weapon's reload state is done sooner than the end of the animation file. The time a player is able to return fire should be the same as before.


A reload loads the magazine only when the whole reload animation has finished, 0.17-0.67 s after the last click of
the reload sound. A player who switches weapons in that gap, for example to the knife after hearing the click, loses
the reload: the magazine is still low when they switch back. Players count shots and listen for the click, so the
click should mean the magazine is in.

With this PR the magazine loads at the gun's last reload click. Nothing else about the reload changes: the animation
plays to its end, and the gun can fire again, aim down sights or lower for a sprint only when the animation ends,
as before. Only a weapon switch in that last stretch behaves differently: it keeps the loaded magazine.

Each gun's weapon script names the click as ReloadReadyBeforeEnd, next to CycleTime: how long before the end of
the reload animation the last click comes. The code subtracts it from the length of the reload sequence it started,
so the value follows the animation if the animation changes. The click is the start of the last loud click in the
reload .wav (the last onset within 12 dB of the file's peak), read from the classic sound files (NT;RE ships the
same files), rounded to the 30 fps animation frame. On the Jitte, SRM, MPN, AA13 and PZ the charging handle is back
in place a little after the click (frames from the classic viewmodels); the script comment names that offset as the
alternative value.

Code, all in CNEOBaseCombatWeapon:

  • Reload(): after DefaultReload starts the reload (owner and weapon next attack at the animation end, as on
    master), it stores the ready time: the animation end minus ReloadReadyBeforeEnd, never before the reload start.
  • ItemBusyFrame(), which runs while the owner waits for the reload animation to end: once the ready time has
    passed, it calls FinishReload().
  • FinishReload(): returns at once if this reload's magazine is already loaded, so the usual CheckReload at the
    animation end does not load a second magazine.
  • The ready time is networked and predicted, so the local client loads the magazine on the same frame.

A gun without the key loads at the animation end, as before; the Supa7 keeps its own per-shell reload.

An animation event at the same frame would carry the value as well, but the reload sequences have no events and
the viewmodels are the classic files, so every gun's viewmodel would need recompiling.

gun reload animation last click handle / mag in place ReloadReadyBeforeEnd alternative (handle)
ZR68S, ZR68C 2.700 2.265 2.267 0.433
ZR68L 2.700 2.524 2.267 0.167
Jitte, Jitte scoped 3.300 2.604 2.900 0.667 0.400
MX, MX silenced 2.867 2.499 2.500 0.367
SRM, SRM-S 3.067 2.804 2.900 0.233 0.167
MPN, MPN unsilenced 3.200 2.684 2.800 0.500 0.400
AA13 4.667 4.190 4.333 0.467 0.333
Kyla 2.367 1.736 1.333 (cylinder) 0.600
M41, M41S, M41L 3.700 3.367 3.367 0.333
Milso 2.100 1.616 1.167 (magazine) 0.467
Tachi 2.100 1.686 1.167 (magazine) 0.400
PZ 5.767 5.128 5.367 0.633 0.400
SRS 3.700 3.063 3.067 0.633

Times in seconds from reload start; offsets in seconds before the animation ends.

Balance: firing is unchanged: the first shot after a reload comes at the end of the reload animation, as on
master (measured below). Aiming down sights and the sprint lower still wait for the end of the animation. The only
change is that the magazine survives a weapon switch made after the last click. Classic NEOTOKYO does not keep it
either: on the ZR68S it loads the magazine at 2.70 s and loses the reload when switching at 2.47 s (the Tachi also
loads at its animation length; the other guns were not measured in classic).

Changed after the first review: the script value is now an offset before the end of the reload animation instead of
an absolute time, and the time the gun can fire again stays at the end of the animation; only the magazine loads at
the click.

How to test

  1. Partly empty a ZR68S, reload, and switch to the knife right after the last click (about 2.3 s). Switch back:
    the magazine is full. Switching before the click still cancels the reload, as before.
  2. Hold fire from the start of a reload: the first shot comes at the end of the reload animation, as on master.
  3. Reload and do nothing: the reload animation plays to its end, then idles; the reserve drops by one magazine.
  4. Supa7 reload, Milso / Tachi reloads from an empty magazine: unchanged.

Results

Listen server on nt_rise_ctg as Jinrai, Release builds of master (f426c22) and this PR. A temporary harness logger
(not part of this PR) printed the reload start, magazine load, animation end, cancel and shot times, and the server
switched to the knife a set time after the reload started and back 0.7 s later.

  • Magazine load on all 20 magazine guns: master at the animation length (+0-1 tick); this PR at the click (+0-1
    tick): ZR68S/C 2.280, ZR68L 2.535, Jitte 2.640, MX 2.505, SRM 2.835, MPN 2.700, AA13 4.200, Kyla 1.770, M41
    3.375, Milso 1.635, Tachi 1.710, PZ 5.145, SRS 3.075. The reserve drops by one magazine only.
  • Switch 0.05 s after the click: master loses the reload on all 20 guns, this PR keeps the full magazine on all 20.
    Switch 0.05 s before it: this PR loses the reload on all 20, as intended.
  • First shot with fire held from the reload start, 3 reloads per gun: this PR = master = the animation length
    (+0-1 tick) on all 20 guns (the semi-automatic AA13 with fire tapped every 40 ms: 4.680-4.725 s on both).
  • Client: the magazine loads on the same tick as on the server.
  • Prediction: listen server with net_fakelag 75 and predicted weapon switches (slot3 / slot1,
    hud_fastswitch 1): the client loads the magazine on the server's tick and stays in the reload animation to its
    end; switches after the click keep the full magazine on both, switches before it lose the reload, and the client
    never showed a full magazine that the server then took back. A client on a dedicated server was not tested.
  • Supa7: shells at the same times as master (0.89, 1.39, 1.91, 2.42, 2.92 s).
  • Debug dedicated server, 12 bots, 1 round each on ntre_dawn_ctg, ntre_threadplate_ctg and ntre_isolation_ctg: no
    asserts.

Toolchain

  • Linux GCC Distro Native (Linux Mint 22.3, GCC 13.3.0)

Linked Issues

  • None.

@sunzenshen sunzenshen added the Gunplay Gunplay elements in Neotokyo. Grenades, TTK, etc. label Oct 15, 2026
@sunzenshen
sunzenshen marked this pull request as ready for review October 16, 2026 07:16
@sunzenshen
sunzenshen requested review from a team and removed request for a team October 16, 2026 07:50
@sunzenshen

Copy link
Copy Markdown
Contributor Author

Demos:

reload-click-switch-jitte.mp4
reload-click-switch-mpn.mp4
reload-click-switch-pz.mp4
reload-click-switch-zr68s.mp4

@sunzenshen
sunzenshen requested a review from a team October 16, 2026 08:38
@AdamTadeusz
AdamTadeusz requested a review from a team October 22, 2026 17:20
A reload credited the magazine only when the whole reload animation
ended, 0.2 to 0.7 s after the last click of the reload sound. Switching
weapons in that window dropped the reload, though the gun sounded done.

Each gun script now names its ReloadReadyTime: the last loud click of
its reload sound, rounded up to the animation frame. Where the charging
handle is home later (Jitte, SRM, MPN, AA13, PZ), the script comment
names that time as the alternative value. The reload completes there
and the rest of the animation plays on. Guns without the key (Supa7)
keep the animation length.
Keep the end of the reload animation as the time the gun can fire
again, as before this branch, and only load the magazine early: the
owner stays busy until the animation ends, and the weapon loads the
magazine from ItemBusyFrame once the reload's ready time has passed.
A switch after the last click keeps the ammo; firing, aiming and the
sprint lower still wait for the animation to end.

The script key is now ReloadReadyBeforeEnd, the time from the last
reload click to the end of the reload animation, and the ready time is
that much before the end of the reload sequence actually played,
clamped to the reload start. The ready time is networked and predicted
so the client loads the magazine on the same frame.

@Agiel Agiel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌🏻

@AdamTadeusz
AdamTadeusz merged commit 1d3ee5e into NeotokyoRebuild:master Oct 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gunplay Gunplay elements in Neotokyo. Grenades, TTK, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants