Partial Weapon class methods to notify third-person bodies about reload steps and completion. It computes reload step counts and durations for magazine vs shell-by-shell reloads and sends NZNet.PlayerReload messages with phase, start time and total length; tracks whether a shell reload is open.
using NZombies;
using System;
namespace SWB.Base;
/// <summary>
/// THE RELOAD ON THE PLAYER'S BODY, FOR EVERY SCREEN (2026-10-05). The user: *"I want the reload animations to also be seen in
/// third person"*.
///
/// ⛔ IT WAS ONE BARE `b_reload`, SENT BEFORE THE RELOAD HAD A LENGTH. `StartReload` sent it ahead of the timing, so the body's
/// clip played at its authored 1.67 s whatever the gun took (0.6 s with Speed Cola, 4 s on an LMG); every round of a shotgun
/// re-sent the same gesture to a graph that answers it once; and nothing ever told the body a reload had ENDED. The body's left
/// hand was also pinned to the handguard every frame (`GunGrip.Hands`), so the reload's own left hand never moved.
///
/// ⚠️ NOW EACH STEP OF A RELOAD GOES OUT WITH ITS LENGTH (`NZNet.PlayerReload`) and the body fits its clip to it
/// (`ThirdPersonWeapon.PlayReload`, `speed_reload`). A magazine reload is one step. A round-at-a-time reload is its opening, a
/// step per round, and its closing — the shotgun graph's own round loop. The gun only reports; what each step looks like is
/// decided on each machine by the hold that body is in.
///
/// ⚠️ THE OWNER'S MACHINE ONLY, like the rest of the weapon (`NetworkMode.Never`); the steps travel owner → everyone.
/// </summary>
public partial class Weapon
{
/// <summary>
/// The bodies were told a round-at-a-time reload is open (its opening went out, its closing has not): the next round is an
/// insert, and a closing is owed. Forgotten when a new one begins (`OnShellReload`).
/// </summary>
bool _bodyShellOpen;
/// <summary>
/// A reload step has begun, <paramref name="seconds"/> long: the timer `StartReload` has just set. Called from there, the first
/// line that knows the length.
/// </summary>
void BodyReloadStep( NZPlayer nz, float seconds, float reloadSpeed )
{
if ( !nz.IsValid() ) return;
var id = nz.GameObject.Id;
if ( !ShellReloading )
{
NZNet.PlayerReload( id, (int)ThirdPersonWeapon.ReloadPhase.Magazine, seconds, seconds );
return;
}
if ( _bodyShellOpen )
{
NZNet.PlayerReload( id, (int)ThirdPersonWeapon.ReloadPhase.ShellInsert, seconds, seconds );
return;
}
_bodyShellOpen = true;
// ⚠️ THE WHOLE RELOAD'S LENGTH GOES WITH THE OPENING, ESTIMATED: a hold with no round loop (a revolver, a tube-fed rifle)
// plays its one reload clip across all of it rather than once per round. This step loads a round itself
// (`OnShellReload`), so the rest are the inserts after it, then the closing clip if the gun has one. The rounds are counted
// the way `OnShellReloadFinish` counts them: Overfill's worth, one insert for a whole-load gun, never more than the reserve.
var over = OverfillRounds();
var rounds = ClassTechWholeLoad ? 1 : over >= 0 ? over : ClassTechClip( Primary.ClipSize ) - Primary.Ammo;
if ( Primary.InfiniteAmmo != InfiniteAmmoType.reserve ) rounds = Math.Min( rounds, Owner.AmmoCount( Primary.AmmoType ) );
rounds = Math.Max( 1, rounds );
var insert = ShellReloadInsertTime / Math.Max( 0.01f, reloadSpeed );
var total = seconds + (rounds - 1) * insert + Math.Max( 0f, ShellReloadEndTime );
NZNet.PlayerReload( id, (int)ThirdPersonWeapon.ReloadPhase.ShellStart, seconds, total );
}
/// <summary>
/// The reload is over on the gun. A round-at-a-time reload closing (<paramref name="seconds"/> = its closing clip, 0 when it
/// has none), or a magazine reload stopped part way (<paramref name="cancelled"/>: the knife, a power-up). A magazine reload
/// that simply finished sends nothing, because its clip was fitted to end with it.
///
/// ⚠️ SENT ONCE. `AsyncShellReloadEnd` calls this as its closing clip starts and `CancelShellReload` again when it is done; the
/// second finds nothing open. `CancelShellReload` also runs on reloads that were never open, which find nothing either.
/// </summary>
void BodyReloadEnded( float seconds, bool cancelled )
{
var shell = _bodyShellOpen;
_bodyShellOpen = false;
if ( !shell && !cancelled ) return;
var nz = Components.Get<NZPlayer>( FindMode.InAncestors | FindMode.Enabled );
if ( !nz.IsValid() ) return;
var phase = shell ? ThirdPersonWeapon.ReloadPhase.ShellEnd : ThirdPersonWeapon.ReloadPhase.Cancel;
NZNet.PlayerReload( nz.GameObject.Id, (int)phase, seconds, seconds );
}
}