private void ClientExplodeAt(WeaponFinalCache weaponCache, Vector2D explosionWorldPosition) { var protoWeapon = weaponCache.ProtoWeapon; var shotSourcePosition = WeaponSystemClientDisplay.SharedCalculateWeaponShotWorldPositon( weaponCache.Character, protoWeapon, weaponCache.Character.ProtoCharacter, weaponCache.Character.Position, hasTrace: true); this.ClientExplodeAt(protoWeapon, shotSourcePosition, explosionWorldPosition); }
private void ClientExplodeAt( IProtoItemWeapon protoWeapon, Vector2D shotSourcePosition, Vector2D explosionWorldPosition) { var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit(protoWeapon.FireTracePreset ?? this.FireTracePreset, shotSourcePosition, explosionWorldPosition); ClientTimersSystem.AddAction(timeToHit, () => ExplosionHelper.ClientExplode(explosionWorldPosition, this.ExplosionPreset, this.ExplosionVolume)); }
private void ServerCreateDroppedArrow(WeaponFinalCache weaponCache, Vector2D endPosition) { var shotSourcePosition = WeaponSystemClientDisplay.SharedCalculateWeaponShotWorldPositon( weaponCache.Character, weaponCache.ProtoWeapon, weaponCache.Character.ProtoCharacter, weaponCache.Character.Position, hasTrace: true); var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit( weaponCache.ProtoWeapon.FireTracePreset ?? weaponCache.ProtoAmmo.FireTracePreset, shotSourcePosition, endPosition); ServerTimersSystem.AddAction( timeToHit, () => { var droplist = ServerGetDroplistFor(weaponCache.ProtoAmmo); var endTilePosition = endPosition.ToVector2Ushort(); var result = ServerDroplistHelper.TryDropToGround( droplist, endTilePosition, // compensate for the server rate to ensure that // it doesn't affect the number of arrows spawned probabilityMultiplier: 1.0 / DropItemsList.DropListItemsCountMultiplier, context: new DropItemContext(weaponCache.Character), out _); if (!result.IsEverythingCreated) { return; } using var charactersObserving = Api.Shared.GetTempList <ICharacter>(); Server.World.GetCharactersInRadius(endTilePosition, charactersObserving, radius: 15, onlyPlayers: true); this.CallClient(charactersObserving.AsList(), _ => _.ClientRemote_OnArrowHitGround(endTilePosition)); }); }
private void ClientRemote_OnArrowHitGround(Vector2Ushort endTilePosition) { var hitPosition = new Vector2D(endTilePosition.X + 0.5, endTilePosition.Y + 0.5); WeaponSystemClientDisplay.ClientAddHitSparks( WeaponHitSparksPresets.Firearm, new WeaponHitData(hitPosition: hitPosition), hitWorldObject: null, protoWorldObject: null, worldObjectPosition: endTilePosition.ToVector2D(), projectilesCount: 1, objectMaterial: ObjectMaterial.Wood, randomizeHitPointOffset: false, randomRotation: false, drawOrder: DrawOrder.Light); }
protected override void ClientExplodeAt( IProtoItemWeapon protoWeapon, Vector2D shotSourcePosition, Vector2D explosionWorldPosition) { var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit(protoWeapon.FireTracePreset ?? this.FireTracePreset, shotSourcePosition, explosionWorldPosition); ClientTimersSystem.AddAction( timeToHit, () => { SharedExplosionHelper.ClientExplode(explosionWorldPosition, this.ExplosionPreset, this.VolumeExplosion); // add more explosions in the X pattern as it's a fragmentation grenade ClientTimersSystem.AddAction( 0.2, () => { SharedExplosionHelper.ClientExplode(explosionWorldPosition + (1, 1), this.ExplosionPreset, volume: 0); SharedExplosionHelper.ClientExplode(explosionWorldPosition + (1, -1), this.ExplosionPreset, volume: 0); SharedExplosionHelper.ClientExplode(explosionWorldPosition + (-1, 1), this.ExplosionPreset, volume: 0); SharedExplosionHelper.ClientExplode(explosionWorldPosition + (-1, -1), this.ExplosionPreset, volume: 0); });
private void ServerExplodeAt(WeaponFinalCache weaponCache, Vector2D endPosition, bool isHit) { var character = weaponCache.Character; var protoWeapon = (IProtoItemWeaponRanged)weaponCache.ProtoWeapon; var shotSourcePosition = WeaponSystemClientDisplay.SharedCalculateWeaponShotWorldPositon( character, protoWeapon, character.ProtoCharacter, character.Position, hasTrace: true); if (isHit) { // offset end position a bit towards the source position // this way the explosion will definitely happen outside the hit object endPosition -= 0.1 * (endPosition - shotSourcePosition).Normalized; } var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit( protoWeapon.FireTracePreset ?? this.FireTracePreset, shotSourcePosition, endPosition); // We're using a check here similar to the one in WeaponSystem. // Ensure that player can hit objects only on the same height level // and can fire through over the pits (the cliffs of the lower heights). var anyCliffIsAnObstacle = character.Tile.Height != Server.World.GetTile(endPosition.ToVector2Ushort()).Height; if (!WeaponSystem.SharedHasTileObstacle( character.Position, character.Tile.Height, endPosition, character.PhysicsBody.PhysicsSpace, anyCliffIsAnObstacle: anyCliffIsAnObstacle)) { ServerTimersSystem.AddAction( timeToHit, () => { ExplosionHelper.ServerExplode( character: character, protoExplosive: this, protoWeapon: protoWeapon, explosionPreset: this.ExplosionPreset, epicenterPosition: endPosition, damageDescriptionCharacters: this.DamageDescription, physicsSpace: Server.World.GetPhysicsSpace(), executeExplosionCallback: this.ServerExecuteExplosion); }); } // notify other characters about the explosion using var charactersObserving = Api.Shared.GetTempList <ICharacter>(); var explosionEventRadius = Math.Max(protoWeapon.SoundPresetWeaponDistance.max, this.FireRangeMax) + this.DamageRadius; Server.World.GetCharactersInRadius(endPosition.ToVector2Ushort(), charactersObserving, radius: (byte)Math.Min(byte.MaxValue, explosionEventRadius), onlyPlayers: true); charactersObserving.Remove(character); this.CallClient(charactersObserving.AsList(), _ => _.ClientRemote_OnExplosion(protoWeapon, shotSourcePosition, endPosition)); }
public override void Update(double deltaTime) { if (this.dronePublicState.TargetObjectPosition.HasValue) { this.lastTargetPosition = this.dronePublicState.TargetObjectPosition.Value.ToVector2D(); if (this.hitWorldObject is null) { this.hitWorldObject = Client.World.GetTile(this.lastTargetPosition.Value.ToVector2Ushort()) .StaticObjects .FirstOrDefault(); } if (this.hitWorldObject != null) { var objectCenter = DroneTargetPositionHelper.GetTargetPosition(this.hitWorldObject); objectCenter = (objectCenter.X, objectCenter.Y * 0.5 + 0.2); this.lastTargetPosition += objectCenter; } } else { this.hitWorldObject = null; } if (this.hitWorldObject != null && this.soundEmitterMiningProcess.SoundResource is null) { this.soundEmitterMiningProcess.SoundResource = GetSourceResourceMiningProcess(this.hitWorldObject.ProtoStaticWorldObject); } var wasEnabled = this.spriteRendererLine.IsEnabled; var isBeamActive = this.dronePublicState.IsMining && this.lastTargetPosition.HasValue; this.alpha = MathHelper.LerpWithDeltaTime( this.alpha, isBeamActive ? 1 : 0, deltaTime: deltaTime, rate: 20); var isEnabled = this.alpha > 0.001; this.spriteRendererLine.IsEnabled = isEnabled; this.spriteRendererOrigin.IsEnabled = isEnabled; this.soundEmitterMiningProcess.IsEnabled = isEnabled; if (!isEnabled) { if (wasEnabled) { // just disabled Api.Client.Audio.PlayOneShot(SourceResourceMiningEnd, this.SceneObject, MiningVolume); } return; } if (!wasEnabled) { // just enabled Api.Client.Audio.PlayOneShot(SourceResourceMiningStart, this.SceneObject, MiningVolume); } var alphaComponent = (byte)(this.alpha * byte.MaxValue); this.spriteRendererLine.Color = this.beamColor.WithAlpha(alphaComponent); this.spriteRendererOrigin.Color = Color.FromArgb(alphaComponent, 0xFF, 0xFF, 0xFF); this.soundEmitterMiningProcess.Volume = (float)(this.alpha * MiningVolume); // update line start-end positions and rotation angle var currentBeamOriginOffset = this.beamOriginOffset + this.primaryRenderer.PositionOffset - this.primaryRendererDefaultPositionOffset; var lineStartWorldPosition = this.SceneObject.Position + currentBeamOriginOffset; // ReSharper disable once PossibleInvalidOperationException var lineEndWorldPosition = this.lastTargetPosition.Value; lineEndWorldPosition += this.GetMiningTargetMovementAnimation(deltaTime); var lineDirection = lineEndWorldPosition - lineStartWorldPosition; this.spriteRendererLine.PositionOffset = currentBeamOriginOffset; this.spriteRendererLine.RotationAngleRad = (float)-Math.Atan2(lineDirection.Y, lineDirection.X); this.spriteRendererLine.Scale = (lineDirection.Length, this.beamWidth); this.spriteRendererLine.DrawOrderOffsetY = this.primaryRenderer.DrawOrderOffsetY; this.spriteRendererOrigin.PositionOffset = currentBeamOriginOffset; this.spriteRendererOrigin.Scale = this.beamWidth; this.spriteRendererOrigin.DrawOrderOffsetY = this.primaryRenderer.DrawOrderOffsetY; if (this.hitWorldObject is null) { return; } this.timeSinceLastHitSparks -= deltaTime; if (this.timeSinceLastHitSparks < 0) { WeaponSystemClientDisplay.ClientAddHitSparks( WeaponHitSparksPresets.LaserMining, new WeaponHitData(Vector2D.Zero), this.hitWorldObject, this.hitWorldObject.ProtoStaticWorldObject, worldObjectPosition: lineEndWorldPosition, projectilesCount: 1, objectMaterial: this.hitWorldObject.ProtoStaticWorldObject.SharedGetObjectMaterial(), randomizeHitPointOffset: false, randomRotation: true, drawOrder: DrawOrder.Light + 2, animationFrameDuration: 3 / 60.0); this.timeSinceLastHitSparks += HitSparksInterval; } }
public static void ClientUpdateAnimation( ICharacter character, BaseCharacterClientState clientState, ICharacterPublicState publicState) { var skeletonRenderer = clientState.SkeletonRenderer; if (skeletonRenderer == null) { return; } var activeWeaponProto = publicState.CurrentItemWeaponProto; var protoSkeleton = clientState.CurrentProtoSkeleton; var rendererShadow = clientState.RendererShadow; var wasDead = clientState.IsDead; clientState.IsDead = publicState.IsDead; if (publicState.IsDead) { if (skeletonRenderer.GetCurrentAnimationName(AnimationTrackIndexes.Primary) == "Death") { // already in death animation return; } // character was not dead on client and now become dead skeletonRenderer.ResetSkeleton(); skeletonRenderer.SelectCurrentSkeleton( protoSkeleton.SkeletonResourceFront, "Idle", isLooped: false); skeletonRenderer.SetAnimation(AnimationTrackIndexes.Primary, "Death", isLooped: false); if (!wasDead.HasValue) { // character entered scope and it's dead skeletonRenderer.SetAnimationTime(0, 10000); // hide skeleton completely HideBody(); return; } // character just died // play death sound protoSkeleton.PlaySound(CharacterSound.Death, character); clientState.SoundEmitterLoopCharacter.Stop(); clientState.SoundEmitterLoopMovemement.Stop(); // hide skeleton after timeout ClientTimersSystem.AddAction( CorpseTimeoutSeconds, HideBody); void HideBody() { if (!publicState.IsDead) { // the character has been respawned return; } skeletonRenderer.IsEnabled = false; rendererShadow.IsEnabled = false; } return; } skeletonRenderer.IsEnabled = true; rendererShadow.IsEnabled = true; var appliedInput = publicState.AppliedInput; var rotationAngleRad = GetCurrentRotationAngleRadInterpolated(character, clientState, appliedInput); GetCurrentAnimationSetting( protoSkeleton, appliedInput.MoveModes, rotationAngleRad, clientState.LastViewOrientation, out var newAnimationStarterName, out var newAnimationName, out var currentDrawMode, out var aimCoef, out var viewOrientation, out var isIdle); clientState.LastViewOrientation = viewOrientation; // TODO: consider adding new field - HasBackwardAnimations if (!protoSkeleton.HasMoveStartAnimations) { newAnimationStarterName = null; if (newAnimationName == "RunSideBackward") { newAnimationName = "RunSide"; } } var currentSkeleton = viewOrientation.IsUp && protoSkeleton.SkeletonResourceBack != null ? protoSkeleton.SkeletonResourceBack : protoSkeleton.SkeletonResourceFront; if (skeletonRenderer.CurrentSkeleton != currentSkeleton) { // switch skeleton completely skeletonRenderer.SelectCurrentSkeleton( currentSkeleton, // please note: no starter animation in that case! animationName: newAnimationName, isLooped: true); } else { var activeAnimationName = skeletonRenderer.GetLatestAddedAnimationName(trackIndex: AnimationTrackIndexes.Primary); if (newAnimationName != activeAnimationName && newAnimationStarterName != activeAnimationName) { //Api.Logger.WriteDev( // newAnimationStarterName != null // ? $"Changing move animation: {activeAnimationName}->{newAnimationStarterName}->{newAnimationName}" // : $"Changing move animation: {activeAnimationName}->{newAnimationName}"); var hasStarterAnimation = newAnimationStarterName != null; if (hasStarterAnimation) { // add starter animation skeletonRenderer.SetAnimation( trackIndex: AnimationTrackIndexes.Primary, animationName: newAnimationStarterName, isLooped: false); // add looped animation skeletonRenderer.AddAnimation( trackIndex: AnimationTrackIndexes.Primary, animationName: newAnimationName, isLooped: true); } else if (newAnimationName == "Idle" && skeletonRenderer.GetCurrentAnimationName(trackIndex: AnimationTrackIndexes.Primary) .EndsWith("Start")) { // going into idle when playing a start animation - allow to finish it! // remove queued entries skeletonRenderer.RemoveAnimationTrackNextEntries(trackIndex: AnimationTrackIndexes.Primary); // add looped idle animation skeletonRenderer.AddAnimation( trackIndex: AnimationTrackIndexes.Primary, animationName: newAnimationName, isLooped: true); } else { // set looped animation skeletonRenderer.SetAnimation( trackIndex: AnimationTrackIndexes.Primary, animationName: newAnimationName, isLooped: true); } } } if (appliedInput.MoveModes != CharacterMoveModes.None) { // moving mode var animationSpeedMultiplier = appliedInput.MoveSpeed / (protoSkeleton.DefaultMoveSpeed * clientState.CurrentProtoSkeletonScaleMultiplier); skeletonRenderer.SetAnimationSpeed( trackIndex: AnimationTrackIndexes.Primary, speedMultiliper: (float)animationSpeedMultiplier); // moving - remove animation for static firing skeletonRenderer.RemoveAnimationTrack(AnimationTrackIndexes.ItemFiringStatic); } else { skeletonRenderer.SetAnimationSpeed( trackIndex: AnimationTrackIndexes.Primary, speedMultiliper: 1.0f); } skeletonRenderer.DrawMode = currentDrawMode; if (activeWeaponProto != null) { clientState.HasWeaponAnimationAssigned = true; clientState.LastAimCoef = aimCoef; var aimingAnimationName = activeWeaponProto.CharacterAnimationAimingName; if (aimingAnimationName != null) { //Api.Logger.WriteDev( // $"Setting aiming animation: {aimingAnimationName} timePercents: {aimCoef:F2}"); skeletonRenderer.SetAnimationFrame( trackIndex: AnimationTrackIndexes.ItemAiming, animationName: aimingAnimationName, timePositionPercents: aimCoef); } else { skeletonRenderer.RemoveAnimationTrack(AnimationTrackIndexes.ItemAiming); } WeaponSystemClientDisplay.RefreshCurrentAttackAnimation(character); } else { clientState.HasWeaponAnimationAssigned = false; } SetLoopSounds(character, clientState, publicState.AppliedInput, protoSkeleton, isIdle); }