From 9955f3f2ef323e3707f93a1810d71d6cd98f9664 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Sat, 16 Nov 2024 02:01:18 -0300 Subject: [PATCH 01/16] Implement osu!stable fail animation --- .../osu/support/slider/SliderBody.java | 5 + .../audio/serviceAudio/BassAudioFunc.java | 27 ++++- .../zuev/audio/serviceAudio/SongService.java | 13 ++ src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 112 +++++++++++++++--- .../ccfit/zuev/osu/game/GameplaySlider.java | 14 ++- 5 files changed, 146 insertions(+), 25 deletions(-) diff --git a/src/com/edlplan/osu/support/slider/SliderBody.java b/src/com/edlplan/osu/support/slider/SliderBody.java index 0f0434430..d5a7867d7 100644 --- a/src/com/edlplan/osu/support/slider/SliderBody.java +++ b/src/com/edlplan/osu/support/slider/SliderBody.java @@ -30,6 +30,11 @@ public class SliderBody extends Container { private float endLength = 0; private boolean shouldRebuildVertices = true; + + + public float gameplayPositionX; + + public float gameplayPositionY; public SliderBody(boolean allowHint) { diff --git a/src/ru/nsu/ccfit/zuev/audio/serviceAudio/BassAudioFunc.java b/src/ru/nsu/ccfit/zuev/audio/serviceAudio/BassAudioFunc.java index b234bdb0f..9387ce2f4 100644 --- a/src/ru/nsu/ccfit/zuev/audio/serviceAudio/BassAudioFunc.java +++ b/src/ru/nsu/ccfit/zuev/audio/serviceAudio/BassAudioFunc.java @@ -30,6 +30,11 @@ public class BassAudioFunc { private BroadcastReceiver receiver; private LocalBroadcastManager broadcastManager; + /** + * The channel's frequency, in Hz. + */ + private float frequency; + /** * Whether the game is currently on focus. */ @@ -105,6 +110,8 @@ public boolean preLoad(String filePath, float speed, boolean adjustPitch) { } BASS.BASS_ChannelGetInfo(channel, channelInfo); + frequency = channelInfo.freq; + setSpeed(speed); setAdjustPitch(adjustPitch); @@ -218,6 +225,19 @@ public void setAdjustPitch(boolean adjustPitch) { onAudioEffectChange(); } + public void setFrequencyForcefully(float frequency) { + if (channel == 0) { + return; + } + this.frequency = frequency; + BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO_FREQ, frequency); + BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO, 0); + } + + public float getFrequency() { + return frequency; + } + public float getVolume() { BASS.FloatValue volume = new BASS.FloatValue(); if (channel != 0) { @@ -268,11 +288,14 @@ private void onAudioEffectChange() { return; } + if (adjustPitch) { - BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO_FREQ, channelInfo.freq * speed); + frequency = channelInfo.freq * speed; + BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO_FREQ, frequency); BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO, 0); } else { - BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO_FREQ, channelInfo.freq); + frequency = channelInfo.freq; + BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO_FREQ, frequency); BASS.BASS_ChannelSetAttribute(channel, BASS_FX.BASS_ATTRIB_TEMPO, (speed - 1) * 100); } } diff --git a/src/ru/nsu/ccfit/zuev/audio/serviceAudio/SongService.java b/src/ru/nsu/ccfit/zuev/audio/serviceAudio/SongService.java index b24f8a263..045200b5d 100644 --- a/src/ru/nsu/ccfit/zuev/audio/serviceAudio/SongService.java +++ b/src/ru/nsu/ccfit/zuev/audio/serviceAudio/SongService.java @@ -235,6 +235,19 @@ public void setAdjustPitch(boolean adjustPitch) { } } + public void setFrequencyForcefully(float frequency) { + if (audioFunc != null) { + audioFunc.setFrequencyForcefully(frequency); + } + } + + public float getFrequency() { + if (audioFunc != null) { + return audioFunc.getFrequency(); + } + return 0f; + } + public void showNotification() { if (this.isGaming) { Log.w("SongService", "NOT SHOW THE NOTIFY CUZ IS GAMING"); diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 35cd2da50..cf95ac58a 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.Job; import kotlinx.coroutines.JobKt; +import ru.nsu.ccfit.zuev.audio.serviceAudio.SongService; import ru.nsu.ccfit.zuev.osu.SecurityUtils; import com.edlplan.framework.easing.Easing; @@ -16,6 +17,7 @@ import com.edlplan.framework.support.ProxySprite; import com.edlplan.framework.support.osb.StoryboardSprite; import com.edlplan.framework.utils.functionality.SmartIterator; +import com.edlplan.osu.support.slider.SliderBody; import com.reco1l.andengine.modifier.UniversalModifier; import com.reco1l.ibancho.RoomAPI; import com.reco1l.osu.DifficultyCalculationManager; @@ -62,6 +64,7 @@ import org.anddev.andengine.engine.handler.IUpdateHandler; import org.anddev.andengine.engine.options.TouchOptions; import org.anddev.andengine.entity.Entity; +import org.anddev.andengine.entity.IEntity; import org.anddev.andengine.entity.modifier.LoopEntityModifier; import org.anddev.andengine.entity.modifier.MoveXModifier; import org.anddev.andengine.entity.primitive.Rectangle; @@ -191,6 +194,14 @@ public class GameScene implements IUpdateHandler, GameObjectListener, private ChangeableText urText; private ChangeableText memText; + // Game + + /** + * Whether the game is over. + */ + private boolean isGameOver = false; + + // UI /** @@ -602,6 +613,7 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina realTimeElapsed = 0; statisticDataTimeElapsed = 0; lastScoreSent = null; + isGameOver = false; paused = false; gameStarted = false; @@ -1223,13 +1235,11 @@ public void onUpdate(final float pSecondsElapsed) { if (GameHelper.isEasy() && failcount < 3) { failcount++; stat.changeHp(1f); - } - else { - if (Multiplayer.isMultiplayer) - { - if (!hasFailed) + } else { + if (Multiplayer.isMultiplayer) { + if (!hasFailed) { ToastLogger.showText("You failed but you can continue playing.", false); - + } hasFailed = true; } else { gameover(); @@ -2198,10 +2208,14 @@ public void pause() { } public void gameover() { - if (Multiplayer.isMultiplayer) - { - if (Multiplayer.isConnected()) - { + + if (isGameOver) { + return; + } + isGameOver = true; + + if (Multiplayer.isMultiplayer) { + if (Multiplayer.isConnected()) { Multiplayer.log("Player has lost, moving to room scene."); Execution.async(() -> Execution.runSafe(() -> RoomAPI.submitFinalScore(stat.toJson()))); } @@ -2219,17 +2233,77 @@ public void gameover() { final PauseMenu menu = new PauseMenu(engine, this, true); gameStarted = false; - if (video != null) { - video.pause(); - } + SongService songService = GlobalManager.getInstance().getSongService(); + float initialFrequency = songService.getFrequency(); - if (GlobalManager.getInstance().getSongService() != null && GlobalManager.getInstance().getSongService().getStatus() == Status.PLAYING) { - GlobalManager.getInstance().getSongService().pause(); - } - paused = true; + // Wind down animation for failing based on osu!stable behavior. - scene.setIgnoreUpdate(true); - hud.setChildScene(menu.getScene(), false, true, true); + scene.registerUpdateHandler(new IUpdateHandler() { + + @Override + public void onUpdate(float pSecondsElapsed) { + if (songService.getFrequency() > 101) { + + for (int i = 0; i < mgScene.getChildCount(); i++) { + IEntity entity = mgScene.getChild(i); + + if (entity.getAlpha() > 0) { + entity.setAlpha(Math.max(0, entity.getAlpha() - 0.007f)); + } + + if (entity instanceof SliderBody sliderBody) { + // Slider body needs special handling since the entity's position doesn't represent the + // actual position because it's real position is represented directly on the path vertices. + float positionY = entity.getY() + sliderBody.gameplayPositionY; + + entity.setPosition(entity.getX(), positionY < 0f + ? (positionY * 0.6f) - sliderBody.gameplayPositionY + : (positionY * 1.01f) - sliderBody.gameplayPositionY + ); + } else { + entity.setPosition(entity.getX(), entity.getY() < 0f ? entity.getY() * 0.6f : entity.getY() * 1.01f); + } + + + if (entity.getRotation() == 0) { + entity.setRotation(entity.getRotation() + (float) Random.Default.nextDouble(-0.02, 0.02)); + } else if (entity.getRotation() > 0) { + entity.setRotation(entity.getRotation() + 0.01f); + } else { + entity.setRotation(entity.getRotation() - 0.01f); + } + } + + float decreasedFrequency = Math.max(101, songService.getFrequency() - 300); + float decreasedSpeed = GameHelper.getSpeedMultiplier() - GameHelper.getSpeedMultiplier() * ((initialFrequency - decreasedFrequency) / initialFrequency); + + scene.setTimeMultiplier(decreasedSpeed); + if (video != null) { + video.setPlaybackSpeed(decreasedSpeed); + } + + songService.setFrequencyForcefully(decreasedFrequency); + } else { + if (video != null) { + video.pause(); + } + + if (GlobalManager.getInstance().getSongService() != null && GlobalManager.getInstance().getSongService().getStatus() == Status.PLAYING) { + GlobalManager.getInstance().getSongService().pause(); + } + paused = true; + + scene.setIgnoreUpdate(true); + scene.unregisterUpdateHandler(this); + + hud.setChildScene(menu.getScene(), false, true, true); + } + } + + @Override + public void reset() { + } + }); } public void resume() { diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java index 50af03517..158728e0a 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java @@ -248,13 +248,13 @@ public void init(final GameObjectListener listener, final Scene scene, float fadeOutDuration = timePreempt * (float) ModHidden.FADE_OUT_DURATION_MULTIPLIER; headCirclePiece.registerEntityModifier(Modifiers.sequence( - Modifiers.fadeIn(fadeInDuration), - Modifiers.fadeOut(fadeOutDuration) + Modifiers.fadeIn(fadeInDuration), + Modifiers.fadeOut(fadeOutDuration) )); tailCirclePiece.registerEntityModifier(Modifiers.sequence( - Modifiers.fadeIn(fadeInDuration), - Modifiers.fadeOut(fadeOutDuration) + Modifiers.fadeIn(fadeInDuration), + Modifiers.fadeOut(fadeOutDuration) )); } else { @@ -311,6 +311,12 @@ public void init(final GameObjectListener listener, final Scene scene, sliderBody.setHintVisible(false); } + // Used exclusively for modifiers. The slider path already has this coordinates + // applied to its vertices (we should eventually change that) and because of that + // the slider body position will always be (0, 0) which is not accurate. + sliderBody.gameplayPositionX = position.x; + sliderBody.gameplayPositionY = position.y; + scene.attachChild(sliderBody, 0); if (Config.isDimHitObjects()) { From e17891496ce9fa2db0a791956191a6dcc024f2d7 Mon Sep 17 00:00:00 2001 From: "Rian (Reza Mouna Hendrian)" <52914632+Rian8337@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:47:39 +0800 Subject: [PATCH 02/16] Simplify `decreasedSpeed` equation --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index cf95ac58a..29baae4a2 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2275,7 +2275,7 @@ public void onUpdate(float pSecondsElapsed) { } float decreasedFrequency = Math.max(101, songService.getFrequency() - 300); - float decreasedSpeed = GameHelper.getSpeedMultiplier() - GameHelper.getSpeedMultiplier() * ((initialFrequency - decreasedFrequency) / initialFrequency); + float decreasedSpeed = GameHelper.getSpeedMultiplier() * (1 - (initialFrequency - decreasedFrequency) / initialFrequency); scene.setTimeMultiplier(decreasedSpeed); if (video != null) { @@ -2288,8 +2288,8 @@ public void onUpdate(float pSecondsElapsed) { video.pause(); } - if (GlobalManager.getInstance().getSongService() != null && GlobalManager.getInstance().getSongService().getStatus() == Status.PLAYING) { - GlobalManager.getInstance().getSongService().pause(); + if (songService.getStatus() == Status.PLAYING) { + songService.pause(); } paused = true; From bd08a9bf5996e1ba25a1df598d32416cb1a9a993 Mon Sep 17 00:00:00 2001 From: "Rian (Reza Mouna Hendrian)" <52914632+Rian8337@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:25:20 +0800 Subject: [PATCH 03/16] Attach fail animation to engine rather than scene to avoid speed multiplier effect --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 29baae4a2..67ea682bf 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2238,8 +2238,7 @@ public void gameover() { // Wind down animation for failing based on osu!stable behavior. - scene.registerUpdateHandler(new IUpdateHandler() { - + engine.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { if (songService.getFrequency() > 101) { @@ -2294,7 +2293,7 @@ public void onUpdate(float pSecondsElapsed) { paused = true; scene.setIgnoreUpdate(true); - scene.unregisterUpdateHandler(this); + engine.unregisterUpdateHandler(this); hud.setChildScene(menu.getScene(), false, true, true); } From de2273ee3ea0f0c84596058d12038fd2cdf66a5c Mon Sep 17 00:00:00 2001 From: "Rian (Reza Mouna Hendrian)" <52914632+Rian8337@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:25:40 +0800 Subject: [PATCH 04/16] Cap animation duration to 60 FPS --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 67ea682bf..c136a420b 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2239,8 +2239,22 @@ public void gameover() { // Wind down animation for failing based on osu!stable behavior. engine.registerUpdateHandler(new IUpdateHandler() { + private float elapsedTime; + @Override public void onUpdate(float pSecondsElapsed) { + elapsedTime += pSecondsElapsed; + + // In osu!stable, the update is capped to 60 FPS. This means in higher framerates, the animations + // need to be slowed down to match 60 FPS. + float sixtyFPS = 1 / 60f; + + if (elapsedTime < sixtyFPS) { + return; + } + + elapsedTime -= sixtyFPS; + if (songService.getFrequency() > 101) { for (int i = 0; i < mgScene.getChildCount(); i++) { From 3a97cb3110ae71903e5561f36a6b725da9a3873c Mon Sep 17 00:00:00 2001 From: "Rian (Reza Mouna Hendrian)" <52914632+Rian8337@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:30:09 +0800 Subject: [PATCH 05/16] Ensure music frequency is reset after animation --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index c136a420b..16971b5ee 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2301,9 +2301,13 @@ public void onUpdate(float pSecondsElapsed) { video.pause(); } + // Ensure music frequency is reset back to what it was. + songService.setFrequencyForcefully(initialFrequency); + if (songService.getStatus() == Status.PLAYING) { songService.pause(); } + paused = true; scene.setIgnoreUpdate(true); From d594269b373a86fabf6994d630c2f6612efca9ac Mon Sep 17 00:00:00 2001 From: "Rian (Reza Mouna Hendrian)" <52914632+Rian8337@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:37:42 +0800 Subject: [PATCH 06/16] Fix missing radians to degrees conversion --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 16971b5ee..2195ee888 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2279,11 +2279,11 @@ public void onUpdate(float pSecondsElapsed) { if (entity.getRotation() == 0) { - entity.setRotation(entity.getRotation() + (float) Random.Default.nextDouble(-0.02, 0.02)); + entity.setRotation(entity.getRotation() + (float) Random.Default.nextDouble(-0.02, 0.02) * 180 / FMath.Pi); } else if (entity.getRotation() > 0) { - entity.setRotation(entity.getRotation() + 0.01f); + entity.setRotation(entity.getRotation() + 0.01f * 180 / FMath.Pi); } else { - entity.setRotation(entity.getRotation() - 0.01f); + entity.setRotation(entity.getRotation() - 0.01f * 180 / FMath.Pi); } } From 35bfb25af8e34d2e071ef0aaa3d0363a95c80509 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 18 Nov 2024 00:47:13 -0300 Subject: [PATCH 07/16] Fix slider body and slider ticks real position --- .../edlplan/osu/support/slider/SliderBody.java | 5 ----- src/com/reco1l/andengine/ExtendedEntity.kt | 5 +++++ src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 15 +-------------- .../nsu/ccfit/zuev/osu/game/GameplaySlider.java | 17 ++++++++++------- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/com/edlplan/osu/support/slider/SliderBody.java b/src/com/edlplan/osu/support/slider/SliderBody.java index d5a7867d7..0f0434430 100644 --- a/src/com/edlplan/osu/support/slider/SliderBody.java +++ b/src/com/edlplan/osu/support/slider/SliderBody.java @@ -30,11 +30,6 @@ public class SliderBody extends Container { private float endLength = 0; private boolean shouldRebuildVertices = true; - - - public float gameplayPositionX; - - public float gameplayPositionY; public SliderBody(boolean allowHint) { diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index 84bbf2516..7173d6386 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -266,6 +266,11 @@ abstract class ExtendedEntity( } } + open fun setTranslation(x: Float, y: Float) { + translationX = x + translationY = y + } + // Drawing diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 2195ee888..2b30c2547 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -17,7 +17,6 @@ import com.edlplan.framework.support.ProxySprite; import com.edlplan.framework.support.osb.StoryboardSprite; import com.edlplan.framework.utils.functionality.SmartIterator; -import com.edlplan.osu.support.slider.SliderBody; import com.reco1l.andengine.modifier.UniversalModifier; import com.reco1l.ibancho.RoomAPI; import com.reco1l.osu.DifficultyCalculationManager; @@ -2264,19 +2263,7 @@ public void onUpdate(float pSecondsElapsed) { entity.setAlpha(Math.max(0, entity.getAlpha() - 0.007f)); } - if (entity instanceof SliderBody sliderBody) { - // Slider body needs special handling since the entity's position doesn't represent the - // actual position because it's real position is represented directly on the path vertices. - float positionY = entity.getY() + sliderBody.gameplayPositionY; - - entity.setPosition(entity.getX(), positionY < 0f - ? (positionY * 0.6f) - sliderBody.gameplayPositionY - : (positionY * 1.01f) - sliderBody.gameplayPositionY - ); - } else { - entity.setPosition(entity.getX(), entity.getY() < 0f ? entity.getY() * 0.6f : entity.getY() * 1.01f); - } - + entity.setPosition(entity.getX(), entity.getY() < 0f ? entity.getY() * 0.6f : entity.getY() * 1.01f); if (entity.getRotation() == 0) { entity.setRotation(entity.getRotation() + (float) Random.Default.nextDouble(-0.02, 0.02) * 180 / FMath.Pi); diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java index 158728e0a..1f8fb0b08 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java @@ -286,14 +286,23 @@ public void init(final GameObjectListener listener, final Scene scene, scene.attachChild(tailCirclePiece, 0); tickContainer.init(beatmapSlider); + tickContainer.setPosition(position.x, position.y); + tickContainer.setTranslation(-position.x, -position.y); scene.attachChild(tickContainer, 0); // Slider track superPath = renderPath; sliderBody.setPath(superPath, Config.isSnakingInSliders()); + + // Slider path vertices already has the slider position applied on it so the entity's + // position should be always at (0, 0). However this is a problem when we want to apply + // transformations to the entity, so we do this workaround to make the entity's position + // (x and y properties) to be the real slider's position (As well for slider ticks). + sliderBody.setPosition(position.x, position.y); + sliderBody.setTranslation(-position.x, -position.y); + sliderBody.setBackgroundWidth(OsuSkin.get().getSliderBodyWidth() * scale); sliderBody.setBackgroundColor(bodyColor.r(), bodyColor.g(), bodyColor.b(), OsuSkin.get().getSliderBodyBaseAlpha()); - sliderBody.setBorderWidth(OsuSkin.get().getSliderBorderWidth() * scale); sliderBody.setBorderColor(borderColor.r(), borderColor.g(), borderColor.b()); @@ -311,12 +320,6 @@ public void init(final GameObjectListener listener, final Scene scene, sliderBody.setHintVisible(false); } - // Used exclusively for modifiers. The slider path already has this coordinates - // applied to its vertices (we should eventually change that) and because of that - // the slider body position will always be (0, 0) which is not accurate. - sliderBody.gameplayPositionX = position.x; - sliderBody.gameplayPositionY = position.y; - scene.attachChild(sliderBody, 0); if (Config.isDimHitObjects()) { From 8480201cfb4e4f8a644223ca8eb35887077c35c8 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 18 Nov 2024 00:56:21 -0300 Subject: [PATCH 08/16] Prevent touch events and pause during game over animation --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 2b30c2547..09bf02f09 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2073,7 +2073,7 @@ private PointF applyCursorTrackCoordinates(Cursor cursor) { public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent event) { - if (replaying) { + if (replaying || isGameOver) { return false; } @@ -2144,7 +2144,8 @@ public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent event) { } public void pause() { - if (paused) { + + if (paused || isGameOver) { return; } @@ -2213,6 +2214,13 @@ public void gameover() { } isGameOver = true; + // Releasing all cursors visually. At this point touch events will no longer be processed. + for (int i = 0; i < CursorCount; ++i) { + if (cursorSprites != null) { + cursorSprites[i].setShowing(false); + } + } + if (Multiplayer.isMultiplayer) { if (Multiplayer.isConnected()) { Multiplayer.log("Player has lost, moving to room scene."); From 93d9d39659a7db4ea565877822af92279762caa3 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 16:12:39 -0300 Subject: [PATCH 09/16] Make slider path independent of slider's position. --- .../edlplan/andengine/TriangleBuilder.java | 41 +++++---- .../osu/support/slider/DrawLinePath.java | 28 +----- .../osu/support/slider/SliderBody.java | 32 ++++--- .../reco1l/andengine/shape/TriangleMesh.kt | 11 +++ .../nsu/ccfit/zuev/osu/game/GameHelper.java | 10 +-- .../ccfit/zuev/osu/game/GameplaySlider.java | 86 +++++++++++-------- 6 files changed, 105 insertions(+), 103 deletions(-) diff --git a/src/com/edlplan/andengine/TriangleBuilder.java b/src/com/edlplan/andengine/TriangleBuilder.java index 6c872c4de..b55863d6a 100644 --- a/src/com/edlplan/andengine/TriangleBuilder.java +++ b/src/com/edlplan/andengine/TriangleBuilder.java @@ -1,16 +1,17 @@ package com.edlplan.andengine; import com.edlplan.framework.math.Vec2; -import com.edlplan.framework.math.Vec3; import com.edlplan.framework.utils.FloatArraySlice; import java.util.Arrays; public class TriangleBuilder extends FloatArraySlice { - public TriangleBuilder(float[] cache) { - this.ary = cache; - } + + public float maxX = 0f; + + public float maxY = 0f; + public TriangleBuilder() { this(1); @@ -20,17 +21,6 @@ public TriangleBuilder(int size) { ary = new float[6 * size]; } - public void add(Vec3 p1, Vec3 p2, Vec3 p3) { - if (length + 6 > ary.length) { - ary = Arrays.copyOf(ary, ary.length * 3 / 2 + 6); - } - ary[length++] = p1.x; - ary[length++] = p1.y; - ary[length++] = p2.x; - ary[length++] = p2.y; - ary[length++] = p3.x; - ary[length++] = p3.y; - } public void add(Vec2 p1, Vec2 p2, Vec2 p3) { if (length + 6 > ary.length) { @@ -42,21 +32,30 @@ public void add(Vec2 p1, Vec2 p2, Vec2 p3) { ary[length++] = p2.y; ary[length++] = p3.x; ary[length++] = p3.y; - } - public float[] copyVertex() { - float[] c = Arrays.copyOf(ary, offset); - return c; + if (p1.x > maxX) maxX = p1.x; + if (p2.x > maxX) maxX = p2.x; + if (p3.x > maxX) maxX = p3.x; + + if (p1.y > maxY) maxY = p1.y; + if (p2.y > maxY) maxY = p2.y; + if (p3.y > maxY) maxY = p3.y; } - public FloatArraySlice getVertex(FloatArraySlice slice) { + public void applyVertices(FloatArraySlice slice) { slice.offset = 0; if (slice.ary.length < length) { slice.ary = new float[length]; } slice.length = length; System.arraycopy(ary, 0, slice.ary, 0, length); - return slice; } + public void reset() { + length = 0; + maxX = 0f; + maxY = 0f; + } + + } diff --git a/src/com/edlplan/osu/support/slider/DrawLinePath.java b/src/com/edlplan/osu/support/slider/DrawLinePath.java index e777ef59a..a194c75ff 100644 --- a/src/com/edlplan/osu/support/slider/DrawLinePath.java +++ b/src/com/edlplan/osu/support/slider/DrawLinePath.java @@ -8,9 +8,6 @@ public class DrawLinePath { private static final int MAXRES = 24; - private static final float Z_MIDDLE = 99.0f; - - private static final float Z_SIDE = -99.0f; public float alpha; public float width; Vec2 current, current2; @@ -24,12 +21,6 @@ public class DrawLinePath { private TriangleBuilder triangles; private AbstractPath path; - public DrawLinePath(AbstractPath p, float width) { - alpha = 1; - path = p; - this.width = width; - } - public DrawLinePath() { alpha = 1; } @@ -44,21 +35,13 @@ public DrawLinePath reset(AbstractPath p, float width) { return this; } - public TriangleBuilder getTriangles() { - if (triangles == null) { - triangles = new TriangleBuilder(path.size() * 6); - init(); - } - return triangles; - } - - public TriangleBuilder getTriangles(TriangleBuilder builder) { + public TriangleBuilder computeTriangles(TriangleBuilder builder) { TriangleBuilder cache = triangles; if (cache != null) { - cache.getVertex(builder); + cache.applyVertices(builder); } else { triangles = builder; - builder.length = 0; + builder.reset(); init(); } triangles = cache; @@ -136,11 +119,8 @@ private void init() { if (path.size() == 1) { addLineCap(path.get(0), FMath.Pi, FMath.Pi); addLineCap(path.get(0), 0, FMath.Pi); - return; - } else { - return; - //throw new RuntimeException("Path must has at least 1 point"); } + return; } float theta = Vec2.calTheta(path.get(0), path.get(1)); diff --git a/src/com/edlplan/osu/support/slider/SliderBody.java b/src/com/edlplan/osu/support/slider/SliderBody.java index 0f0434430..f331733df 100644 --- a/src/com/edlplan/osu/support/slider/SliderBody.java +++ b/src/com/edlplan/osu/support/slider/SliderBody.java @@ -4,6 +4,7 @@ import com.edlplan.framework.math.line.LinePath; import com.reco1l.andengine.shape.TriangleMesh; import com.reco1l.andengine.container.Container; +import com.rian.osu.math.Vector2; public class SliderBody extends Container { @@ -59,7 +60,7 @@ public SliderBody(boolean allowHint) { } - public void setPath(LinePath path, boolean beginEmpty) { + public void init(LinePath path, boolean beginEmpty, Vector2 position) { reset(); this.path = path; @@ -73,6 +74,7 @@ public void setPath(LinePath path, boolean beginEmpty) { } shouldRebuildVertices = true; + setPosition(position.x, position.y); } @@ -114,24 +116,32 @@ public void setBorderColor(float r, float g, float b) { } - private void buildVertices(LinePath sub) { + private void buildVertices(LinePath subPath) { + + TriangleBuilder builder = buildCache.triangleBuilder; if (hint != null && hint.isVisible()) { buildCache.drawLinePath - .reset(sub, Math.min(hintWidth, backgroundWidth - borderWidth)) - .getTriangles(buildCache.triangleBuilder) - .getVertex(hint.getVertices()); + .reset(subPath, Math.min(hintWidth, backgroundWidth - borderWidth)) + .computeTriangles(builder) + .applyVertices(hint.getVertices()); + + hint.setContentSize(builder.maxX, builder.maxY); } buildCache.drawLinePath - .reset(sub, backgroundWidth - borderWidth) - .getTriangles(buildCache.triangleBuilder) - .getVertex(background.getVertices()); + .reset(subPath, backgroundWidth - borderWidth) + .computeTriangles(builder) + .applyVertices(background.getVertices()); + + background.setContentSize(builder.maxX, builder.maxY); buildCache.drawLinePath - .reset(sub, backgroundWidth) - .getTriangles(buildCache.triangleBuilder) - .getVertex(border.getVertices()); + .reset(subPath, backgroundWidth) + .computeTriangles(builder) + .applyVertices(border.getVertices()); + + border.setContentSize(builder.maxX, builder.maxY); } diff --git a/src/com/reco1l/andengine/shape/TriangleMesh.kt b/src/com/reco1l/andengine/shape/TriangleMesh.kt index 349f85d6b..1a987ea4a 100644 --- a/src/com/reco1l/andengine/shape/TriangleMesh.kt +++ b/src/com/reco1l/andengine/shape/TriangleMesh.kt @@ -15,12 +15,23 @@ class TriangleMesh : ExtendedEntity(vertexBuffer = null) { */ val vertices = FloatArraySlice() + init { isCullingEnabled = false vertices.ary = FloatArray(0) } + /** + * Allows to set the content size of the mesh explicitly. + */ + fun setContentSize(width: Float, height: Float) { + contentWidth = width + contentHeight = height + onContentSizeMeasured() + } + + override fun onInitDraw(pGL: GL10) { super.onInitDraw(pGL) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java b/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java index 09966001c..2a7540d4c 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java @@ -98,23 +98,15 @@ public static LinePath convertSliderPath(final SliderPath sliderPath) { * @return The converted {@link SliderPath}. */ public static SliderPath convertSliderPath(final Slider slider) { - var startPosition = slider.getPosition(); - var gameplayStackOffset = slider.getGameplayStackOffset(); - var calculatedPath = slider.getPath().getCalculatedPath(); var cumulativeLength = slider.getPath().getCumulativeLength(); var path = new SliderPath(calculatedPath.size()); - var tmpPoint = new PointF(); for (var i = 0; i < calculatedPath.size(); i++) { var p = calculatedPath.get(i); - tmpPoint.set(startPosition.x + p.x, startPosition.y + p.y); - - // The path is already flipped when the library applies the Hard Rock mod, so we don't need to do it here. - Utils.trackToRealCoords(tmpPoint); - path.setPoint(i, tmpPoint.x + gameplayStackOffset.x, tmpPoint.y + gameplayStackOffset.y); + path.setPoint(i, p.x, p.y); if (i < cumulativeLength.size()) { path.setLength(i, cumulativeLength.get(i).floatValue()); diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java index 1f8fb0b08..8b6ab1d1a 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java @@ -39,7 +39,6 @@ public class GameplaySlider extends GameObject { private final ExtendedSprite approachCircle; private final ExtendedSprite startArrow, endArrow; private Slider beatmapSlider; - private final PointF curveEndPos = new PointF(); private Scene scene; private GameObjectListener listener; private SliderPath path; @@ -79,6 +78,11 @@ public class GameplaySlider extends GameObject { private final SliderBody sliderBody; + /** + * The absolute slider's path end position. + * This already takes into account the absolute object's position. + */ + private final PointF pathEndPosition = new PointF(); /** * The slider ball sprite. @@ -217,8 +221,7 @@ public void init(final GameObjectListener listener, final Scene scene, } // End circle - curveEndPos.x = path.getX(path.pointCount - 1); - curveEndPos.y = path.getY(path.pointCount - 1); + pathEndPosition.set(getAbsolutePathPosition(path.pointCount - 1)); tailCirclePiece.setScale(scale); tailCirclePiece.setCircleColor(comboColor.r(), comboColor.g(), comboColor.b()); @@ -227,7 +230,7 @@ public void init(final GameObjectListener listener, final Scene scene, if (Config.isSnakingInSliders()) { tailCirclePiece.setPosition(this.position.x, this.position.y); } else { - tailCirclePiece.setPosition(curveEndPos.x, curveEndPos.y); + tailCirclePiece.setPosition(pathEndPosition.x, pathEndPosition.y); } // Repeat arrow at start @@ -235,9 +238,11 @@ public void init(final GameObjectListener listener, final Scene scene, if (spanCount > 2) { startArrow.setAlpha(0); startArrow.setScale(scale); - startArrow.setRotation(MathUtils.radToDeg(Utils.direction(this.position.x, this.position.y, path.getX(1), path.getY(1)))); startArrow.setPosition(this.position.x, this.position.y); + PointF nextPoint = getAbsolutePathPosition(1); + startArrow.setRotation(MathUtils.radToDeg(Utils.direction(position.x, position.y, nextPoint.x, nextPoint.y))); + scene.attachChild(startArrow, 0); } @@ -273,12 +278,14 @@ public void init(final GameObjectListener listener, final Scene scene, if (spanCount > 1) { endArrow.setAlpha(0); endArrow.setScale(scale); - endArrow.setRotation(MathUtils.radToDeg(Utils.direction(curveEndPos.x, curveEndPos.y, path.getX(path.pointCount - 2), path.getY(path.pointCount - 2)))); + + PointF previousPoint = getAbsolutePathPosition(path.pointCount - 2); + endArrow.setRotation(MathUtils.radToDeg(Utils.direction(pathEndPosition.x, pathEndPosition.y, previousPoint.x, previousPoint.y))); if (Config.isSnakingInSliders()) { endArrow.setPosition(this.position.x, this.position.y); } else { - endArrow.setPosition(curveEndPos.x, curveEndPos.y); + endArrow.setPosition(pathEndPosition.x, pathEndPosition.y); } scene.attachChild(endArrow, 0); @@ -292,15 +299,7 @@ public void init(final GameObjectListener listener, final Scene scene, // Slider track superPath = renderPath; - sliderBody.setPath(superPath, Config.isSnakingInSliders()); - - // Slider path vertices already has the slider position applied on it so the entity's - // position should be always at (0, 0). However this is a problem when we want to apply - // transformations to the entity, so we do this workaround to make the entity's position - // (x and y properties) to be the real slider's position (As well for slider ticks). - sliderBody.setPosition(position.x, position.y); - sliderBody.setTranslation(-position.x, -position.y); - + sliderBody.init(superPath, Config.isSnakingInSliders(), stackedPosition); sliderBody.setBackgroundWidth(OsuSkin.get().getSliderBodyWidth() * scale); sliderBody.setBackgroundColor(bodyColor.r(), bodyColor.g(), bodyColor.b(), OsuSkin.get().getSliderBodyBaseAlpha()); sliderBody.setBorderWidth(OsuSkin.get().getSliderBorderWidth() * scale); @@ -383,21 +382,21 @@ public void init(final GameObjectListener listener, final Scene scene, } private PointF getPositionAt(final float percentage, final boolean updateBallAngle, final boolean updateEndArrowRotation) { - if (path.pointCount < 2) { - tmpPoint.set(position); + tmpPoint.set(position); + + if (path.pointCount < 2 || percentage >= 1) { return tmpPoint; } - if (percentage >= 1) { - tmpPoint.set(curveEndPos); - return tmpPoint; - } else if (percentage <= 0) { + if (percentage <= 0) { + PointF nextPoint = getAbsolutePathPosition(1); + if (updateBallAngle) { - ballAngle = MathUtils.radToDeg(Utils.direction(path.getX(1), path.getY(1), position.x, position.y)); + ballAngle = MathUtils.radToDeg(Utils.direction(nextPoint.x, nextPoint.y, position.x, position.y)); } if (updateEndArrowRotation) { - endArrow.setRotation(MathUtils.radToDeg(Utils.direction(position.x, position.y, path.getX(1), path.getY(1)))); + endArrow.setRotation(MathUtils.radToDeg(Utils.direction(position.x, position.y, nextPoint.x, nextPoint.y))); } tmpPoint.set(position); @@ -425,15 +424,15 @@ private PointF getPositionAt(final float percentage, final boolean updateBallAng int index = left - 1; float lengthProgress = (currentLength - path.getLength(index)) / (path.getLength(index + 1) - path.getLength(index)); - var currentPointX = path.getX(index); - var currentPointY = path.getY(index); - - var nextPointX = path.getX(index + 1); - var nextPointY = path.getY(index + 1); + PointF currentPoint = getAbsolutePathPosition(index); + var currentPointX = currentPoint.x; + var currentPointY = currentPoint.y; - var p = tmpPoint; + PointF nextPoint = getAbsolutePathPosition(index + 1); + var nextPointX = nextPoint.x; + var nextPointY = nextPoint.y; - p.set( + tmpPoint.set( Interpolation.linear(currentPointX, nextPointX, lengthProgress), Interpolation.linear(currentPointY, nextPointY, lengthProgress) ); @@ -446,7 +445,7 @@ private PointF getPositionAt(final float percentage, final boolean updateBallAng endArrow.setRotation(MathUtils.radToDeg(Utils.direction(nextPointX, nextPointY, currentPointX, currentPointY))); } - return p; + return tmpPoint; } private void removeFromScene() { @@ -529,7 +528,7 @@ private void onSpanFinish() { boolean isTracking = isTracking(); // If slider was in reverse mode, we should swap start and end points - var spanEndJudgementPosition = reverse ? position : curveEndPos; + var spanEndJudgementPosition = reverse ? position : pathEndPosition; // Do not judge slider repeats if the slider head has not been hit. if (startHit) { @@ -807,13 +806,12 @@ public void update(final float dt) { } if (path.pointCount >= 2) { - endArrow.setRotation( - MathUtils.radToDeg(Utils.direction(curveEndPos.x, curveEndPos.y, path.getX(path.pointCount - 2), path.getY(path.pointCount - 2))) - ); + PointF lastPoint = getAbsolutePathPosition(path.pointCount - 2); + endArrow.setRotation(MathUtils.radToDeg(Utils.direction(pathEndPosition.x, pathEndPosition.y, lastPoint.x, lastPoint.y))); } - tailCirclePiece.setPosition(curveEndPos.x, curveEndPos.y); - endArrow.setPosition(curveEndPos.x, curveEndPos.y); + tailCirclePiece.setPosition(pathEndPosition.x, pathEndPosition.y); + endArrow.setPosition(pathEndPosition.x, pathEndPosition.y); } } return; @@ -1231,4 +1229,16 @@ public void tryHit(final float dt) { } } + + /** + * Gets the absolute position of a point on the path taking into account the slider's position. + */ + private PointF getAbsolutePathPosition(int pathPointIndex) { + tmpPoint.set( + position.x + path.getX(pathPointIndex), + position.y + path.getY(pathPointIndex) + ); + return tmpPoint; + } + } From e65a164c99e7b8933243b1d56564c2ad8e1b1a83 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:02:48 -0300 Subject: [PATCH 10/16] Fix slider size and attach slider ticks to the slider body --- src/com/reco1l/osu/hitobjects/SliderTicks.kt | 15 ++++++++------- src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java | 14 +++++++++++++- .../ccfit/zuev/osu/game/GameplaySlider.java | 18 +++--------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/com/reco1l/osu/hitobjects/SliderTicks.kt b/src/com/reco1l/osu/hitobjects/SliderTicks.kt index e79f9dcf4..1b852a846 100644 --- a/src/com/reco1l/osu/hitobjects/SliderTicks.kt +++ b/src/com/reco1l/osu/hitobjects/SliderTicks.kt @@ -13,21 +13,22 @@ class SliderTickContainer : Container() { detachChildren() - val scale = beatmapSlider.gameplayScale + val position = beatmapSlider.gameplayStackedPosition for (i in 1 until beatmapSlider.nestedHitObjects.size) { - val sliderTick = beatmapSlider.nestedHitObjects[i] as? SliderTick ?: break - val position = sliderTick.gameplayStackedPosition - val sprite = SliderTickSprite.pool.obtain() + val tick = beatmapSlider.nestedHitObjects[i] as? SliderTick ?: break + val tickPosition = tick.gameplayStackedPosition - sprite.setPosition(position.x, position.y) - sprite.setScale(scale) + val sprite = SliderTickSprite.pool.obtain() sprite.alpha = 0f + // We're substracting the position of the slider because the tick container is + // already at the position of the slider since it's a child of the slider's body. + sprite.setPosition(tickPosition.x - position.x, tickPosition.y - position.y) + attachChild(sprite) } - } override fun onDetached() { diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java b/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java index 2a7540d4c..360cdf54c 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java @@ -98,15 +98,27 @@ public static LinePath convertSliderPath(final SliderPath sliderPath) { * @return The converted {@link SliderPath}. */ public static SliderPath convertSliderPath(final Slider slider) { + var startPosition = slider.getPosition(); + var gameplayStartPosition = slider.getGameplayPosition(); + var gameplayStackOffset = slider.getGameplayStackOffset(); + var calculatedPath = slider.getPath().getCalculatedPath(); var cumulativeLength = slider.getPath().getCumulativeLength(); var path = new SliderPath(calculatedPath.size()); + var tmpPoint = new PointF(); for (var i = 0; i < calculatedPath.size(); i++) { var p = calculatedPath.get(i); - path.setPoint(i, p.x, p.y); + tmpPoint.set(startPosition.x + p.x, startPosition.y + p.y); + + // The path is already flipped when the library applies the Hard Rock mod, so we don't need to do it here. + Utils.trackToRealCoords(tmpPoint); + path.setPoint(i, + tmpPoint.x + gameplayStackOffset.x - gameplayStartPosition.x, + tmpPoint.y + gameplayStackOffset.y - gameplayStartPosition.y + ); if (i < cumulativeLength.size()) { path.setLength(i, cumulativeLength.get(i).floatValue()); diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java index 8b6ab1d1a..fd836646b 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java @@ -292,11 +292,6 @@ public void init(final GameObjectListener listener, final Scene scene, } scene.attachChild(tailCirclePiece, 0); - tickContainer.init(beatmapSlider); - tickContainer.setPosition(position.x, position.y); - tickContainer.setTranslation(-position.x, -position.y); - scene.attachChild(tickContainer, 0); - // Slider track superPath = renderPath; sliderBody.init(superPath, Config.isSnakingInSliders(), stackedPosition); @@ -319,6 +314,9 @@ public void init(final GameObjectListener listener, final Scene scene, sliderBody.setHintVisible(false); } + tickContainer.init(beatmapSlider); + sliderBody.attachChild(tickContainer); + scene.attachChild(sliderBody, 0); if (Config.isDimHitObjects()) { @@ -366,16 +364,6 @@ public void init(final GameObjectListener listener, final Scene scene, sliderBody.getBlue(), 1f ) )); - - tickContainer.setColor(colorDim, colorDim, colorDim); - tickContainer.registerEntityModifier(Modifiers.sequence( - Modifiers.delay(dimDelaySec), - Modifiers.color(0.1f, - tickContainer.getRed(), 1f, - tickContainer.getGreen(), 1f, - tickContainer.getBlue(), 1f - ) - )); } applyBodyFadeAdjustments(fadeInDuration); From a628042ea34a71a6583692731e33cee272748f3d Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:07:27 -0300 Subject: [PATCH 11/16] Change scene alpha rather than entity alpha --- src/com/reco1l/andengine/ExtendedEntity.kt | 5 +++++ src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index 7173d6386..1f8cfa507 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -323,6 +323,11 @@ abstract class ExtendedEntity( blue *= parent.blue alpha *= parent.alpha + // We'll assume at this point there's no need to keep multiplying. + if (red == 0f || green == 0f || blue == 0f || alpha == 0f) { + break + } + parent = parent.parent } } diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 09bf02f09..2e59bf46b 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2264,13 +2264,13 @@ public void onUpdate(float pSecondsElapsed) { if (songService.getFrequency() > 101) { + if (mgScene.getAlpha() > 0) { + mgScene.setAlpha(Math.max(0, mgScene.getAlpha() - 0.007f)); + } + for (int i = 0; i < mgScene.getChildCount(); i++) { IEntity entity = mgScene.getChild(i); - if (entity.getAlpha() > 0) { - entity.setAlpha(Math.max(0, entity.getAlpha() - 0.007f)); - } - entity.setPosition(entity.getX(), entity.getY() < 0f ? entity.getY() * 0.6f : entity.getY() * 1.01f); if (entity.getRotation() == 0) { From 6ba2242b4246cfaf52a4467f3f7fa154fa2cc394 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:15:57 -0300 Subject: [PATCH 12/16] Remove unused rectangle --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 2e59bf46b..7d81c9f69 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -928,13 +928,6 @@ protected void onManagedDraw(GL10 pGL, Camera pCamera) { timeOffset += 0.25f; } - - Rectangle kiaiRect = new Rectangle(0, 0, Config.getRES_WIDTH(), - Config.getRES_HEIGHT()); - kiaiRect.setVisible(false); - kiaiRect.setColor(1, 1, 1); - bgScene.attachChild(kiaiRect, 0); - Sprite unranked = new Sprite(0, 0, ResourceManager.getInstance().getTexture("play-unranked")); unranked.setPosition((float) Config.getRES_WIDTH() / 2 - unranked.getWidth() / 2, 80); unranked.setVisible(false); From c7960f1222c11a9dc479f38e75ce58d0299277c9 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:22:07 -0300 Subject: [PATCH 13/16] Prevent entering break during game over animation --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 7d81c9f69..a5750cd68 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -1183,32 +1183,35 @@ public void onUpdate(final float pSecondsElapsed) { GameHelper.setKiai(activeEffectPoint.isKiai); GameHelper.setCurrentBeatTime(Math.max(0, elapsedTime - activeTimingPoint.time / 1000) % GameHelper.getBeatLength()); - if (!breakPeriods.isEmpty()) { - if (!breakAnimator.isBreak() - && breakPeriods.peek().getStart() <= elapsedTime) { - gameStarted = false; - breakAnimator.init(breakPeriods.peek().getLength()); - if(GameHelper.isFlashLight()){ - flashlightSprite.onBreak(true); - } + if (!isGameOver) { + + if (!breakPeriods.isEmpty()) { + if (!breakAnimator.isBreak() && breakPeriods.peek().getStart() <= elapsedTime) { + gameStarted = false; + breakAnimator.init(breakPeriods.peek().getLength()); + if(GameHelper.isFlashLight()){ + flashlightSprite.onBreak(true); + } - if (Multiplayer.isConnected()) - RoomScene.INSTANCE.getChat().show(); + if (Multiplayer.isConnected()) + RoomScene.INSTANCE.getChat().show(); - hud.setHealthBarVisibility(false); - breakPeriods.poll(); + hud.setHealthBarVisibility(false); + breakPeriods.poll(); + } } - } - if (breakAnimator.isOver()) { - // Ensure the chat is dismissed if it's still shown - RoomScene.INSTANCE.getChat().dismiss(); + if (breakAnimator.isOver()) { - gameStarted = true; - hud.setHealthBarVisibility(true); + // Ensure the chat is dismissed if it's still shown + RoomScene.INSTANCE.getChat().dismiss(); + + gameStarted = true; + hud.setHealthBarVisibility(true); - if(GameHelper.isFlashLight()){ - flashlightSprite.onBreak(false); + if(GameHelper.isFlashLight()){ + flashlightSprite.onBreak(false); + } } } @@ -2241,6 +2244,26 @@ public void gameover() { engine.registerUpdateHandler(new IUpdateHandler() { private float elapsedTime; + private void applyEffectToScene(Scene scene) { + if (scene.getAlpha() > 0) { + scene.setAlpha(Math.max(0, scene.getAlpha() - 0.007f)); + } + + for (int i = 0; i < scene.getChildCount(); i++) { + IEntity entity = scene.getChild(i); + + entity.setPosition(entity.getX(), entity.getY() < 0f ? entity.getY() * 0.6f : entity.getY() * 1.01f); + + if (entity.getRotation() == 0) { + entity.setRotation(entity.getRotation() + (float) Random.Default.nextDouble(-0.02, 0.02) * 180 / FMath.Pi); + } else if (entity.getRotation() > 0) { + entity.setRotation(entity.getRotation() + 0.01f * 180 / FMath.Pi); + } else { + entity.setRotation(entity.getRotation() - 0.01f * 180 / FMath.Pi); + } + } + } + @Override public void onUpdate(float pSecondsElapsed) { elapsedTime += pSecondsElapsed; @@ -2257,23 +2280,8 @@ public void onUpdate(float pSecondsElapsed) { if (songService.getFrequency() > 101) { - if (mgScene.getAlpha() > 0) { - mgScene.setAlpha(Math.max(0, mgScene.getAlpha() - 0.007f)); - } - - for (int i = 0; i < mgScene.getChildCount(); i++) { - IEntity entity = mgScene.getChild(i); - - entity.setPosition(entity.getX(), entity.getY() < 0f ? entity.getY() * 0.6f : entity.getY() * 1.01f); - - if (entity.getRotation() == 0) { - entity.setRotation(entity.getRotation() + (float) Random.Default.nextDouble(-0.02, 0.02) * 180 / FMath.Pi); - } else if (entity.getRotation() > 0) { - entity.setRotation(entity.getRotation() + 0.01f * 180 / FMath.Pi); - } else { - entity.setRotation(entity.getRotation() - 0.01f * 180 / FMath.Pi); - } - } + applyEffectToScene(mgScene); + applyEffectToScene(bgScene); float decreasedFrequency = Math.max(101, songService.getFrequency() - 300); float decreasedSpeed = GameHelper.getSpeedMultiplier() * (1 - (initialFrequency - decreasedFrequency) / initialFrequency); From 7e18cf8716b92b797d8d09c2c2457a88b94b6892 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:36:07 -0300 Subject: [PATCH 14/16] Fix slider ball returning to origin when the slider ends --- src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java index fd836646b..1ac652d8a 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java @@ -372,7 +372,12 @@ public void init(final GameObjectListener listener, final Scene scene, private PointF getPositionAt(final float percentage, final boolean updateBallAngle, final boolean updateEndArrowRotation) { tmpPoint.set(position); - if (path.pointCount < 2 || percentage >= 1) { + if (path.pointCount < 2) { + return tmpPoint; + } + + if (percentage >= 1) { + tmpPoint.set(pathEndPosition); return tmpPoint; } From c4cc1597a9be35cb98e8664b2e3d6ff4742c5563 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:48:17 -0300 Subject: [PATCH 15/16] Allow to show the menu before game over animation ends --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index a5750cd68..c7a891161 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -2141,7 +2141,7 @@ public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent event) { public void pause() { - if (paused || isGameOver) { + if (paused) { return; } @@ -2164,6 +2164,12 @@ public void pause() { return; } + if (isGameOver) { + // Finishing the game over animation now. + GlobalManager.getInstance().getSongService().setFrequencyForcefully(101); + return; + } + if (video != null && videoStarted) { video.pause(); } From 7411936843eb3c139e35bfd9abb86b84663cc2b5 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Wed, 20 Nov 2024 21:51:22 -0300 Subject: [PATCH 16/16] Simplify slider path conversion --- src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java b/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java index 360cdf54c..625b20ad3 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameHelper.java @@ -10,6 +10,7 @@ import java.math.RoundingMode; import java.util.Arrays; +import ru.nsu.ccfit.zuev.osu.Constants; import ru.nsu.ccfit.zuev.skins.OsuSkin; import ru.nsu.ccfit.zuev.osu.Utils; import ru.nsu.ccfit.zuev.osu.helper.DifficultyHelper; @@ -98,27 +99,18 @@ public static LinePath convertSliderPath(final SliderPath sliderPath) { * @return The converted {@link SliderPath}. */ public static SliderPath convertSliderPath(final Slider slider) { - var startPosition = slider.getPosition(); - var gameplayStartPosition = slider.getGameplayPosition(); - var gameplayStackOffset = slider.getGameplayStackOffset(); - var calculatedPath = slider.getPath().getCalculatedPath(); var cumulativeLength = slider.getPath().getCumulativeLength(); var path = new SliderPath(calculatedPath.size()); - var tmpPoint = new PointF(); + + float realWidthScale = (float) Constants.MAP_ACTUAL_WIDTH / Constants.MAP_WIDTH; + float realHeightScale = (float) Constants.MAP_ACTUAL_HEIGHT / Constants.MAP_HEIGHT; for (var i = 0; i < calculatedPath.size(); i++) { var p = calculatedPath.get(i); - tmpPoint.set(startPosition.x + p.x, startPosition.y + p.y); - - // The path is already flipped when the library applies the Hard Rock mod, so we don't need to do it here. - Utils.trackToRealCoords(tmpPoint); - path.setPoint(i, - tmpPoint.x + gameplayStackOffset.x - gameplayStartPosition.x, - tmpPoint.y + gameplayStackOffset.y - gameplayStartPosition.y - ); + path.setPoint(i, p.x * realWidthScale, p.y * realHeightScale); if (i < cumulativeLength.size()) { path.setLength(i, cumulativeLength.get(i).floatValue());