public static void SharedUpdateReloading( WeaponState weaponState, ICharacter character, double deltaTime, out bool isReloadingNow) { var reloadingState = weaponState.WeaponReloadingState; if (reloadingState is null) { isReloadingNow = false; return; } if (reloadingState.Item != weaponState.ItemWeapon) { Logger.Info("Can reload only the current weapon. Reloading aborted"); SharedTryAbortReloading(character, reloadingState.Item); isReloadingNow = false; return; } if (IsServer && weaponState.SharedGetInputIsFiring() && weaponState.ItemWeapon is not null && weaponState.ItemWeapon.GetPrivateState <WeaponPrivateState>().AmmoCount > 0) { var shotsRemains = (long)weaponState.ServerLastClientReportedShotsDoneCount - weaponState.ShotsDone; if (shotsRemains > 0 && weaponState.ServerLastClientReportedShotsDoneCount > 0) { // sometimes the client reloading requests are received by the server // too early (before the server fired all the shots) so the server should fire them first //Logger.Dev("Server cannot reload while client is firing. Shot remains: " + shotsRemains); isReloadingNow = false; return; } } // process reloading reloadingState.SecondsToReloadRemains -= deltaTime; if (reloadingState.SecondsToReloadRemains > 0) { // need more time to reload isReloadingNow = true; return; } // reloaded reloadingState.SecondsToReloadRemains = 0; SharedProcessWeaponReload(character, weaponState, out var isAmmoTypeChanged); if (isAmmoTypeChanged) { weaponState.ClearFiringStateData(); //Api.Logger.Dev("Reset ServerLastClientReportedShotsDoneCount. Last value: " // + weaponState.ServerLastClientReportedShotsDoneCount); } weaponState.FirePatternCooldownSecondsRemains = 0; weaponState.IsIdleAutoReloadingAllowed = true; isReloadingNow = false; }
public static void SharedUpdateCurrentWeapon( ICharacter character, WeaponState state, double deltaTime) { var protoWeapon = state.ActiveProtoWeapon; if (protoWeapon == null) { return; } if (state.CooldownSecondsRemains > 0) { // decrease cooldown state.CooldownSecondsRemains -= deltaTime; } if (!state.IsFiring) { WeaponAmmoSystem.SharedUpdateReloading(state, character, ref deltaTime); } if (deltaTime <= 0) { // the weapon reloading process is consumed the whole delta time return; } if (state.SharedGetInputIsFiring() && !character.IsOnline) { state.SetInputIsFiring(false); } if (state.SharedGetInputIsFiring() && StatusEffectDazed.SharedIsCharacterDazed(character, StatusEffectDazed.NotificationCannotAttackWhileDazed)) { state.SetInputIsFiring(false); } // check ammo (if applicable to this weapon prototype) var canFire = protoWeapon.SharedCanFire(character, state); if (state.CooldownSecondsRemains > 0) { // firing cooldown is not completed if (!state.SharedGetInputIsFiring() && state.IsEventWeaponStartSent) { // not firing anymore SharedCallOnWeaponInputStop(state, character); } return; } var wasFiring = state.IsFiring; if (!state.IsFiring) { state.IsFiring = state.SharedGetInputIsFiring(); } else // if IsFiring { if (!SharedShouldFireMore(state)) { state.IsFiring = state.SharedGetInputIsFiring(); } } if (!canFire) { // cannot fire (no ammo, etc) state.IsFiring = false; } if (!state.IsFiring) { if (wasFiring) { // just stopped firing SharedCallOnWeaponFinished(state, character); } // the character is not firing // reset delay for the next shot (it will be set when firing starts next time) state.DamageApplyDelaySecondsRemains = 0; return; } // let's process what happens when we're in the firing mode if (!state.IsEventWeaponStartSent) { // started firing SharedCallOnWeaponStart(state, character); } if (state.DamageApplyDelaySecondsRemains <= 0) { // initialize delay to next shot state.DamageApplyDelaySecondsRemains = protoWeapon.DamageApplyDelay; SharedCallOnWeaponShot(character); } // decrease the remaining time to the damage application state.DamageApplyDelaySecondsRemains -= deltaTime; if (state.DamageApplyDelaySecondsRemains > 0) { // firing delay not completed return; } // firing delay completed state.ShotsDone++; //Logger.Dev("Weapon fired, shots done: " + state.ShotsDone); SharedFireWeapon(character, state.ActiveItemWeapon, protoWeapon, state); state.CooldownSecondsRemains += protoWeapon.FireInterval - protoWeapon.DamageApplyDelay; if (!protoWeapon.IsLoopedAttackAnimation) { // we don't want to stuck this animation in the last frame // that's fix for the issue: // "Fix extended animation "stuck" issue for mobs (like limbs stuck in the end position and movement animation appears broken)" state.IsEventWeaponStartSent = false; } }
public static void SharedUpdateCurrentWeapon( ICharacter character, WeaponState state, double deltaTime) { var protoWeapon = state.ProtoWeapon; if (protoWeapon == null) { return; } if (deltaTime > 0.4) { // too large delta time probably due to a frame skip deltaTime = 0.4; } if (state.CooldownSecondsRemains > 0) { state.CooldownSecondsRemains -= deltaTime; if (state.CooldownSecondsRemains < -0.2) { // clamp the remaining cooldown in case of a frame skip state.CooldownSecondsRemains = -0.2; } } if (state.ReadySecondsRemains > 0) { state.ReadySecondsRemains -= deltaTime; } if (state.FirePatternCooldownSecondsRemains > 0) { state.FirePatternCooldownSecondsRemains -= deltaTime; if (state.FirePatternCooldownSecondsRemains <= 0) { state.FirePatternCurrentShotNumber = 0; } } // TODO: restore this condition when we redo UI countdown animation for ViewModelHotbarItemWeaponOverlayControl.ReloadDurationSeconds //if (state.CooldownSecondsRemains <= 0) //{ WeaponAmmoSystem.SharedUpdateReloading(state, character, deltaTime); //} if (Api.IsServer && !character.ServerIsOnline && state.SharedGetInputIsFiring()) { state.SharedSetInputIsFiring(false); } // check ammo (if applicable to this weapon prototype) var canFire = (Api.IsClient || character.ServerIsOnline) && state.WeaponReloadingState is null && protoWeapon.SharedCanFire(character, state); if (state.CooldownSecondsRemains > 0) { // firing cooldown is not completed if (!state.SharedGetInputIsFiring() && state.IsEventWeaponStartSent) { // not firing anymore SharedCallOnWeaponInputStop(state, character); } return; } var wasFiring = state.IsFiring; if (!state.IsFiring) { state.IsFiring = state.SharedGetInputIsFiring(); } else // if IsFiring { if (!SharedShouldFireMore(state)) { state.IsFiring = state.SharedGetInputIsFiring(); } } if (!canFire) { // cannot fire (no ammo, etc) state.IsFiring = false; } if (!state.IsFiring) { if (wasFiring) { // just stopped firing SharedCallOnWeaponFinished(state, character); } // the character is not firing // reset delay for the next shot (it will be set when firing starts next time) state.DamageApplyDelaySecondsRemains = 0; return; } if (state.WeaponCache is null) { SharedRebuildWeaponCache(character, state); } // let's process what happens when we're in the firing mode if (!state.IsEventWeaponStartSent) { // started firing SharedCallOnWeaponStart(state, character); } if (state.DamageApplyDelaySecondsRemains <= 0) { // initialize delay to next shot state.DamageApplyDelaySecondsRemains = Shared.RoundDurationByServerFrameDuration(protoWeapon.DamageApplyDelay); SharedCallOnWeaponShot(character, protoWeapon); } // decrease the remaining time to the damage application state.DamageApplyDelaySecondsRemains -= deltaTime; if (state.DamageApplyDelaySecondsRemains > 0) { // firing delay not completed return; } // firing delay completed state.ShotsDone++; //Logger.Dev("Weapon fired, shots done: " + state.ShotsDone); SharedFireWeapon(character, state.ItemWeapon, protoWeapon, state); var cooldownDuration = Shared.RoundDurationByServerFrameDuration(protoWeapon.FireInterval) - Shared.RoundDurationByServerFrameDuration(protoWeapon.DamageApplyDelay); //Logger.Dev($"Cooldown adding: {cooldownDuration} for {protoWeapon}"); state.CooldownSecondsRemains += cooldownDuration; if (!protoWeapon.IsLoopedAttackAnimation) { // we don't want to stuck this animation in the last frame // that's fix for the issue: // "Fix extended animation "stuck" issue for mobs (like limbs stuck in the end position and movement animation appears broken)" state.IsEventWeaponStartSent = false; } }