private static void ServerCheckFiredShotsMismatch(WeaponState state, ICharacter character) { var ammoConsumptionPerShot = state.ProtoWeapon.AmmoConsumptionPerShot; if (ammoConsumptionPerShot == 0) { // weapon doesn't use any ammo - no problem with possible desync return; } if (!WeaponAmmoSystem.IsResetsShotsDoneNumberOnReload(state.ProtoWeapon)) { // this weapon can keep firing after the reload on the server side return; } var requestedShotsCount = state.ServerLastClientReportedShotsDoneCount; if (!requestedShotsCount.HasValue) { return; } var extraShotsDone = (int)(state.ShotsDone - (long)requestedShotsCount.Value); state.ServerLastClientReportedShotsDoneCount = null; if (extraShotsDone == 0) { return; } if (extraShotsDone < 0) { // should never happen as server should fire as much as client requested, always return; } var itemWeapon = state.ItemWeapon; if (itemWeapon == null) { return; } Logger.Important($"Shots count mismatch: requested={requestedShotsCount} actualShotsDone={state.ShotsDone}", character); Instance.CallClient(character, _ => _.ClientRemote_FixAmmoCount(itemWeapon, extraShotsDone)); }
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; } }