protected void SharedApplyInput( ICharacter character, PlayerCharacterPrivateState privateState, PlayerCharacterPublicState publicState) { var characterIsOffline = !character.ServerIsOnline; if (characterIsOffline) { privateState.Input = default; } var vehicle = publicState.CurrentVehicle; if (!(vehicle is null)) { if (!vehicle.IsInitialized) { return; } var protoVehicle = (IProtoVehicle)vehicle.ProtoGameObject; protoVehicle.SharedApplyInput(vehicle, character, privateState, publicState); return; } // please note - input is a structure so actually we're implicitly copying it here var input = privateState.Input; // please note - applied input is a class and we're getting it by reference here var appliedInput = publicState.AppliedInput; var hasRunningFlag = (input.MoveModes & CharacterMoveModes.ModifierRun) != 0; var isRunning = hasRunningFlag; if (isRunning) { var stats = publicState.CurrentStatsExtended; var wasRunning = (appliedInput.MoveModes & CharacterMoveModes.ModifierRun) != 0; var staminaCurrent = stats.StaminaCurrent; if (!wasRunning) { // can start running only when the current energy is at least on 10% isRunning = staminaCurrent >= 0.1 * stats.StaminaMax; } else // if was running { // can continue to run while has energy isRunning = staminaCurrent > 0; } //Logger.WriteDev($"Was running: {wasRunning}; now running: {isRunning}; stamina: {staminaCurrent}"); } double moveSpeed; if (characterIsOffline || (privateState.CurrentActionState?.IsBlocksMovement ?? false)) { // offline or current action blocks movement moveSpeed = 0; isRunning = false; input.MoveModes = CharacterMoveModes.None; } else { var characterFinalStateCache = privateState.FinalStatsCache; moveSpeed = characterFinalStateCache[StatName.MoveSpeed]; moveSpeed *= ProtoTile.SharedGetTileMoveSpeedMultiplier(character.Tile); if (isRunning) { var moveSpeedMultiplier = characterFinalStateCache[StatName.MoveSpeedRunMultiplier]; if (moveSpeedMultiplier > 0) { moveSpeed = moveSpeed * moveSpeedMultiplier; } else { isRunning = false; } } } if (!isRunning) { // cannot run - remove running flag input.MoveModes &= ~CharacterMoveModes.ModifierRun; } if (appliedInput.MoveModes == input.MoveModes && appliedInput.RotationAngleRad == input.RotationAngleRad && publicState.AppliedInput.MoveSpeed == moveSpeed) { // input is not changed return; } // apply new input appliedInput.Set(input, moveSpeed); var moveModes = input.MoveModes; double directionX = 0, directionY = 0; if ((moveModes & CharacterMoveModes.Up) != 0) { directionY = 1; } if ((moveModes & CharacterMoveModes.Down) != 0) { directionY = -1; } if ((moveModes & CharacterMoveModes.Left) != 0) { directionX = -1; } if ((moveModes & CharacterMoveModes.Right) != 0) { directionX = 1; } if (directionX == 0 && directionY == 0) { moveSpeed = 0; } Vector2D directionVector = (directionX, directionY); var moveAcceleration = directionVector.Normalized * this.PhysicsBodyAccelerationCoef * moveSpeed; if (IsServer) { Server.World.SetDynamicObjectPhysicsMovement(character, moveAcceleration, targetVelocity: moveSpeed); character.PhysicsBody.Friction = this.PhysicsBodyFriction; } else // if client { if (ClientCurrentCharacterLagPredictionManager.IsLagPredictionEnabled) { Client.World.SetDynamicObjectPhysicsMovement(character, moveAcceleration, targetVelocity: moveSpeed); character.PhysicsBody.Friction = this.PhysicsBodyFriction; } } }
protected void SharedApplyInput( ICharacter character, PlayerCharacterPrivateState privateState, PlayerCharacterPublicState publicState) { var characterIsOffline = !character.IsOnline; if (characterIsOffline) { privateState.Input = default; } // please note - input is a structure so actually we're implicitly copying it here var input = privateState.Input; // please note - applied input is a class and we're getting it by reference here var appliedInput = publicState.AppliedInput; var hasRunningFlag = (input.MoveModes & CharacterMoveModes.ModifierRun) != 0; var isRunning = hasRunningFlag; if (isRunning) { var stats = publicState.CurrentStatsExtended; var wasRunning = (appliedInput.MoveModes & CharacterMoveModes.ModifierRun) != 0; var staminaCurrent = stats.StaminaCurrent; if (!wasRunning) { // can start running only when the current energy is at least on 10% isRunning = staminaCurrent >= 0.1 * stats.StaminaMax; } else // if was running { // can continue to run while has energy isRunning = staminaCurrent > 0; } //Logger.WriteDev($"Was running: {wasRunning}; now running: {isRunning}; stamina: {staminaCurrent}"); } double moveSpeed; if (characterIsOffline) { moveSpeed = 0; isRunning = false; } else { var characterFinalStateCache = privateState.FinalStatsCache; moveSpeed = characterFinalStateCache[StatName.MoveSpeed]; moveSpeed *= ProtoTile.SharedGetTileMoveSpeedMultiplier(character.Tile); if (isRunning) { var moveSpeedMultiplier = characterFinalStateCache[StatName.MoveSpeedRunMultiplier]; if (moveSpeedMultiplier > 0) { moveSpeed = moveSpeed * moveSpeedMultiplier; } else { isRunning = false; } } } if (!isRunning) { // cannot run - remove running flag input.MoveModes &= ~CharacterMoveModes.ModifierRun; } if (appliedInput.MoveModes == input.MoveModes && appliedInput.RotationAngleRad == input.RotationAngleRad && publicState.AppliedInput.MoveSpeed == moveSpeed) { // input is not changed return; } // apply new input appliedInput.Set(input, moveSpeed); var moveModes = input.MoveModes; double directionX = 0, directionY = 0; if ((moveModes & CharacterMoveModes.Up) != 0) { directionY = 1; } if ((moveModes & CharacterMoveModes.Down) != 0) { directionY = -1; } if ((moveModes & CharacterMoveModes.Left) != 0) { directionX = -1; } if ((moveModes & CharacterMoveModes.Right) != 0) { directionX = 1; } Vector2D directionVector = (directionX, directionY); var moveVelocity = directionVector.Normalized * moveSpeed; if (IsServer) { Server.Characters.SetVelocity(character, moveVelocity); } else // if client { if (ClientCurrentCharacterLagPredictionManager.IsLagPredictionEnabled) { Client.Characters.SetVelocity(character, moveVelocity); } } }
protected BaseActionState(ICharacter character) { this.Character = character; this.CharacterPrivateState = PlayerCharacter.GetPrivateState(character); this.CharacterPublicState = PlayerCharacter.GetPublicState(character); }
/// <summary> /// Shared (current character on client, server) update of /// player character's stamina (every frame, called directly from PlayerCharacter). /// </summary> public static void SharedUpdate( ICharacter character, PlayerCharacterPublicState publicState, PlayerCharacterPrivateState privateState, double deltaTime) { if (!character.ServerIsOnline || privateState.IsDespawned || publicState.IsDead) { return; } var stats = publicState.CurrentStatsExtended; // restore stamina slower when no food or water var staminaRestoreMultiplier = stats.FoodCurrent <= 0 || stats.WaterCurrent <= 0 ? 0.33 : 1; var finalStatsCache = privateState.FinalStatsCache; var statStaminaRegeneration = finalStatsCache[StatName.StaminaRegenerationPerSecond]; // update character stats if (publicState.AppliedInput.MoveModes != CharacterMoveModes.None && publicState.CurrentVehicle is null) { if ((publicState.AppliedInput.MoveModes & CharacterMoveModes.ModifierRun) != 0) { // character is running - consume stamina var staminaConsumption = finalStatsCache[StatName.RunningStaminaConsumptionPerSecond] * deltaTime; if (staminaConsumption > 0) { stats.SharedSetStaminaCurrent((float)(stats.StaminaCurrent - staminaConsumption), notifyClient: false); } if (Api.IsServer) { // TODO: check if character really moved and not running into a wall privateState.Skills.ServerAddSkillExperience <SkillAthletics>( SkillAthletics.ExperienceAddWhenRunningPerSecond * deltaTime); } } else // if (!privateState.Input.MoveModes.HasFlag(CharacterMoveModes.ModifierRun)) { // character is moving but not running (and client is not requested running) - restore stamina at special rate var staminaRestore = StaminaRegenerationWhenMovingMultiplier * statStaminaRegeneration * staminaRestoreMultiplier * deltaTime; stats.SharedSetStaminaCurrent((float)(stats.StaminaCurrent + staminaRestore), notifyClient: false); } } else { // character is staying or in a vehicle - restore stamina var staminaRestore = statStaminaRegeneration * staminaRestoreMultiplier * deltaTime; stats.SharedSetStaminaCurrent((float)(stats.StaminaCurrent + staminaRestore), notifyClient: false); } }