// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; namespace osu.Game.Screens.Play { public partial class KiaiGameplayFountains : BeatSyncedContainer { private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; private Bindable kiaiStarFountains = null!; private Sample? sample; private SampleChannel? sampleChannel; [BackgroundDependencyLoader] private void load(OsuConfigManager config, AudioManager audio) { kiaiStarFountains = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; Children = new[] { leftFountain = new GameplayStarFountain { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, X = 75, }, rightFountain = new GameplayStarFountain { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, X = -75, }, }; sample = audio.Samples.Get(@"Gameplay/fountain-shoot"); } private bool isTriggered; protected override void Update() { base.Update(); if (!kiaiStarFountains.Value) return; if (EffectPoint.KiaiMode && !isTriggered) { bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - EffectPoint.Time) < 500; if (isNearEffectPoint) Shoot(); } isTriggered = EffectPoint.KiaiMode; } public void Shoot() { leftFountain.Shoot(1); rightFountain.Shoot(-1); // Track sample channel to avoid overlapping playback sampleChannel?.Stop(); sampleChannel = sample?.GetChannel(); sampleChannel?.Play(); } public partial class GameplayStarFountain : StarFountain { protected override StarFountainSpewer CreateSpewer() => new GameplayStarFountainSpewer(); private partial class GameplayStarFountainSpewer : StarFountainSpewer { protected override double ShootDuration => 400; public GameplayStarFountainSpewer() : base(perSecond: 180) { } protected override float GetCurrentAngle() { const float x_velocity_from_direction = 450; const float x_velocity_to_direction = 600; return LastShootDirection * RNG.NextSingle(x_velocity_from_direction, x_velocity_to_direction); } } } } }