using Content.Shared._Sunrise.Animations; using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Content.Shared.Climbing.Systems; using Content.Shared.Emoting; using Content.Shared.Gravity; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Standing; using Content.Shared.StatusEffectNew; using Content.Shared.Throwing; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Player; using Robust.Shared.Random; using Content.Shared.Movement.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Content.Shared.Damage.Systems; using Content.Shared.Stunnable; using Content.Shared.Damage.Components; using Content.Shared.Ghost; using Content.Shared.Buckle; namespace Content.Shared._Sunrise.Jump; public abstract partial class SharedJumpSystem : EntitySystem { [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly StandingStateSystem _standingState = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly ClimbSystem _climb = default!; [Dependency] private readonly PullingSystem _pulling = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedStaminaSystem _staminaSystem = default!; [Dependency] private readonly SharedBuckleSystem _buckle = default!; private EntityQuery _physicsQuery; private EntityQuery _fixturesQuery; private static readonly EntProtoId JumpStatusEffectKey = "StatusEffectJump"; private static readonly ProtoId EmoteJumpProto = "Jump"; private static readonly SoundSpecifier JumpSound = new SoundPathSpecifier("/Audio/_Sunrise/jump_mario.ogg"); private readonly List _ignoredRecipients = []; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); SubscribeNetworkEvent(OnClientOptionJumpSound); SubscribeLocalEvent(OnRefreshMoveSpeed); SubscribeLocalEvent(CheckEmote); SubscribeLocalEvent>(OnStandAttempt); _physicsQuery = GetEntityQuery(); _fixturesQuery = GetEntityQuery(); _cfg.OnValueChanged(SunriseCCVars.SunriseCCVars.JumpEnable, OnJumpEnableChanged, true); _cfg.OnValueChanged(SunriseCCVars.SunriseCCVars.BunnyHopEnable, OnBunnyHopEnableChanged, true); _cfg.OnValueChanged(SunriseCCVars.SunriseCCVars.BunnyHopMinSpeedThreshold, OnBunnyHopMinSpeedThresholdChanged, true); _cfg.OnValueChanged(SunriseCCVars.SunriseCCVars.BunnyHopSpeedBoostWindow, OnBunnyHopSpeedBoostWindowChanged, true); _cfg.OnValueChanged(SunriseCCVars.SunriseCCVars.BunnyHopSpeedUpPerJump, OnBunnyHopSpeedUpPerJumpChanged, true); _cfg.OnValueChanged(SunriseCCVars.SunriseCCVars.BunnyHopSpeedLimit, OnBunnyHopSpeedLimitChanged, true); } public override void Update(float frameTime) { base.Update(frameTime); if (!_bunnyHopEnabled) return; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var bunnyHop, out var physics)) { var timeSinceLastLand = _timing.CurTime - bunnyHop.LastLandingTime; var currentSpeed = physics.LinearVelocity.Length(); if (timeSinceLastLand > _bunnyHopSpeedBoostWindow || currentSpeed < _bunnyHopMinSpeedThreshold) { RemComp(uid); _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); } } } private void CheckEmote(EntityUid uid, EmoteAnimationComponent component, BeforeEmoteEvent args) { if (args.Emote != EmoteJumpProto) return; if (!HasComp(uid)) return; if (!CanJump(uid)) args.Cancel(); } private static void OnRefreshMoveSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args) { if (ent.Comp.CanBunnyHop) args.ModifySpeed(ent.Comp.SpeedMultiplier, ent.Comp.SpeedMultiplier); } private void OnStartup(Entity ent, ref StatusEffectAppliedEvent args) { if (!TryComp(args.Target, out var jump)) return; if (jump.IsOnlyEmotion) return; if (!TryComp(args.Target, out var stamina)) return; if (!_physicsQuery.TryGetComponent(args.Target, out var body) || !_fixturesQuery.TryGetComponent(args.Target, out var fixtures)) return; if (_net.IsServer) { _audio.PlayEntity(JumpSound, Filter.Pvs(args.Target).RemovePlayers(_ignoredRecipients), args.Target, true, AudioParams.Default.WithVolume(-5f)); _staminaSystem.TakeStaminaDamage(args.Target, jump.StaminaDamage * stamina.CritThreshold, null, args.Target, args.Target, ignoreResist: true); } EnsureComp(args.Target); _physics.SetBodyStatus(args.Target, body, BodyStatus.InAir); foreach (var (id, fixture) in fixtures.Fixtures) { jump.OriginalCollisionMasks[id] = fixture.CollisionMask; jump.OriginalCollisionLayers[id] = fixture.CollisionLayer; _physics.RemoveCollisionMask(args.Target, id, fixture, (int)CollisionGroup.MidImpassable, manager: fixtures); } // There is a problem with synchronization speed reduction due to stamina with increased bhop speed. // This leads to lag. // TryBunnyHop(args.Target, body); } private void OnShutdown(Entity ent, ref StatusEffectRemovedEvent args) { if (!TryComp(args.Target, out var jump)) return; if (jump.IsOnlyEmotion) return; if (!_physicsQuery.TryComp(args.Target, out var body) || !_fixturesQuery.TryComp(args.Target, out var fixtures)) return; RemCompDeferred(args.Target); _physics.SetBodyStatus(args.Target, body, BodyStatus.OnGround); foreach (var (id, fixture) in fixtures.Fixtures) { if (jump.OriginalCollisionMasks.TryGetValue(id, out var originalMask)) { _physics.SetCollisionMask(args.Target, id, fixture, originalMask, manager: fixtures); } if (jump.OriginalCollisionLayers.TryGetValue(id, out var originalLayer)) { _physics.SetCollisionLayer(args.Target, id, fixture, originalLayer, manager: fixtures); } } if (TryComp(args.Target, out var bunnyHopComp)) bunnyHopComp.LastLandingTime = _timing.CurTime; } private void OnStandAttempt(Entity ent, ref StatusEffectRelayedEvent args) { args.Args.Handled = true; } public bool CanJump(EntityUid uid) { if (!TryComp(uid, out var jump)) return false; if (!_enabled) return false; if (_gravity.IsWeightless(uid)) return false; if (_standingState.IsDown(uid)) return false; if (!_mobState.IsAlive(uid)) return false; if (HasComp(uid)) return false; if (_climb.IsClimbing(uid)) return false; if (_buckle.IsBuckled(uid)) return false; if (jump.IsOnlyEmotion) return true; if (!TryComp(uid, out var stamina)) return false; var threshold = stamina.CritThreshold * (1 - jump.MinimumStamina); if (stamina.StaminaDamage >= threshold) return false; return true; } public bool TryJump(EntityUid uid) { if (!CanJump(uid)) return false; Jump(uid); return true; } public void Jump(EntityUid uid) { if (!TryComp(uid, out var jump)) return; _statusEffects.TryAddStatusEffectDuration(uid, JumpStatusEffectKey, jump.JumpInAirTime); } private bool TryBunnyHop(Entity ent, PhysicsComponent body) { if (!_bunnyHopEnabled) return false; if (TryComp(ent, out var pull) && _pulling.IsPulling(ent, pull)) return false; var currentSpeed = body.LinearVelocity.Length(); if (currentSpeed < _bunnyHopMinSpeedThreshold) return false; var bunnyHopComp = EnsureComp(ent); bunnyHopComp.LastLandingTime = _timing.CurTime; var timeSinceLastLand = _timing.CurTime - bunnyHopComp.LastLandingTime; if (timeSinceLastLand <= _bunnyHopSpeedBoostWindow) { var speedMultiplier = bunnyHopComp.SpeedMultiplier += _bunnyHopSpeedUpPerJump; bunnyHopComp.SpeedMultiplier = Math.Min(speedMultiplier, _bunnyHopSpeedLimit); _movementSpeedModifier.RefreshMovementSpeedModifiers(ent); } return true; } }