From d9cfe83cd54861a68ecc159ed5372522478ab004 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 03:07:55 -0300 Subject: [PATCH 01/85] Account child scale for constraint container --- .../andengine/container/ConstraintContainer.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/com/reco1l/andengine/container/ConstraintContainer.kt b/src/com/reco1l/andengine/container/ConstraintContainer.kt index 159257aca..0b5659e13 100644 --- a/src/com/reco1l/andengine/container/ConstraintContainer.kt +++ b/src/com/reco1l/andengine/container/ConstraintContainer.kt @@ -12,6 +12,12 @@ import org.anddev.andengine.entity.shape.* open class ConstraintContainer : Container() { + /** + * Whether the container should account for the scales of the children when calculating the draw position. + */ + var accountForScaleAxes = Axes.Both + + private val constraints = mutableMapOf() @@ -25,6 +31,8 @@ open class ConstraintContainer : Container() { if (target == this) { targetX = 0f targetWidth = getPaddedWidth() + } else if (accountForScaleAxes.isHorizontal) { + targetWidth *= target.scaleX } val anchorOffsetX = targetWidth * child.anchor.x @@ -37,6 +45,10 @@ open class ConstraintContainer : Container() { childX *= getPaddedWidth() - targetX } + if (target != this && accountForScaleAxes.isHorizontal) { + childX += targetWidth * (1 - target.scaleX) + } + return targetX + childX + child.originOffsetX + anchorOffsetX + child.translationX } @@ -50,6 +62,8 @@ open class ConstraintContainer : Container() { if (target == this) { targetY = 0f targetHeight = getPaddedHeight() + } else if (accountForScaleAxes.isVertical) { + targetHeight *= target.scaleY } val anchorOffsetY = targetHeight * child.anchor.y @@ -62,6 +76,10 @@ open class ConstraintContainer : Container() { childY *= getPaddedHeight() - targetY } + if (target != this && accountForScaleAxes.isVertical) { + childY += targetHeight * (1 - target.scaleY) + } + return targetY + childY + child.originOffsetY + anchorOffsetY + child.translationY } From 6af654c278d20ec8d0e1b605b4962e87914c57ef Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 03:08:13 -0300 Subject: [PATCH 02/85] Invalidate child transforms when constraint changed --- .../reco1l/andengine/container/ConstraintContainer.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/com/reco1l/andengine/container/ConstraintContainer.kt b/src/com/reco1l/andengine/container/ConstraintContainer.kt index 0b5659e13..5e1924bfd 100644 --- a/src/com/reco1l/andengine/container/ConstraintContainer.kt +++ b/src/com/reco1l/andengine/container/ConstraintContainer.kt @@ -117,4 +117,13 @@ open class ConstraintContainer : Container() { removeConstraint(child as? ExtendedEntity) } + override fun onChildPositionChanged(child: IEntity) { + constraints.forEach { (source, target) -> + if (target == child) { + source.invalidateTransformations() + } + } + super.onChildPositionChanged(child) + } + } \ No newline at end of file From 5f44c9d33d7a214184219bfc3f4a04f4af764224 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 03:34:29 -0300 Subject: [PATCH 03/85] Invalidate child transforms when constraint changed --- src/com/reco1l/andengine/container/ConstraintContainer.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/reco1l/andengine/container/ConstraintContainer.kt b/src/com/reco1l/andengine/container/ConstraintContainer.kt index 5e1924bfd..24dbb8e5b 100644 --- a/src/com/reco1l/andengine/container/ConstraintContainer.kt +++ b/src/com/reco1l/andengine/container/ConstraintContainer.kt @@ -121,6 +121,7 @@ open class ConstraintContainer : Container() { constraints.forEach { (source, target) -> if (target == child) { source.invalidateTransformations() + onChildPositionChanged(source) } } super.onChildPositionChanged(child) From 2e5363a69fa15a876f4790bd1bee0b5413fb7a4e Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 03:34:47 -0300 Subject: [PATCH 04/85] Make transform invalidation public --- src/com/reco1l/andengine/ExtendedEntity.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index 0efd144c6..bb2017c87 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -366,7 +366,7 @@ abstract class ExtendedEntity( } } - protected open fun invalidateTransformations() { + open fun invalidateTransformations() { mLocalToParentTransformationDirty = true mParentToLocalTransformationDirty = true } @@ -842,9 +842,7 @@ abstract class ExtendedEntity( val transformedX = localX - boundEntity.getDrawX() val transformedY = localY - boundEntity.getDrawY() - boundEntity.onAreaTouched(event, transformedX, transformedY) - - if (event.isActionUp || event.isActionOutside || event.isActionCancel) { + if (!boundEntity.onAreaTouched(event, transformedX, transformedY)) { currentBoundEntity = null } return true @@ -860,9 +858,7 @@ abstract class ExtendedEntity( val transformedY = localY - child.getDrawY() if (child.onAreaTouched(event, transformedX, transformedY)) { - if (event.isActionDown) { - currentBoundEntity = child - } + currentBoundEntity = child return true } } From 49dcfda2d1d64e68d1c633b416b703aa920fe9f2 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 03:38:03 -0300 Subject: [PATCH 05/85] Initial implementation of HUD layout editor --- src/com/reco1l/osu/hud/GameplayHUD.kt | 156 ++++++++++++++++++ src/com/reco1l/osu/hud/data/HUDLayoutData.kt | 82 +++++++++ .../osu/hud/elements/HUDAccuracyCounter.kt | 25 +++ .../elements/HUDComboCounter.kt} | 86 ++-------- src/com/reco1l/osu/hud/elements/HUDElement.kt | 153 +++++++++++++++++ .../elements/HUDHealthBar.kt} | 28 ++-- .../reco1l/osu/hud/elements/HUDPPCounter.kt | 27 +++ .../elements/HUDPieSongProgress.kt} | 15 +- .../osu/hud/elements/HUDScoreCounter.kt | 26 +++ src/com/reco1l/osu/playfield/GameplayHUD.kt | 126 -------------- src/ru/nsu/ccfit/zuev/osu/Config.java | 2 +- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 31 ++-- 12 files changed, 522 insertions(+), 235 deletions(-) create mode 100644 src/com/reco1l/osu/hud/GameplayHUD.kt create mode 100644 src/com/reco1l/osu/hud/data/HUDLayoutData.kt create mode 100644 src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt rename src/com/reco1l/osu/{playfield/Counters.kt => hud/elements/HUDComboCounter.kt} (63%) create mode 100644 src/com/reco1l/osu/hud/elements/HUDElement.kt rename src/com/reco1l/osu/{playfield/HealthBar.kt => hud/elements/HUDHealthBar.kt} (87%) create mode 100644 src/com/reco1l/osu/hud/elements/HUDPPCounter.kt rename src/com/reco1l/osu/{playfield/CircularSongProgress.kt => hud/elements/HUDPieSongProgress.kt} (74%) create mode 100644 src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt delete mode 100644 src/com/reco1l/osu/playfield/GameplayHUD.kt diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt new file mode 100644 index 000000000..ad800cc08 --- /dev/null +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -0,0 +1,156 @@ +package com.reco1l.osu.hud + +import android.util.Log +import androidx.annotation.* +import com.reco1l.andengine.Axes +import com.reco1l.andengine.container.ConstraintContainer +import com.reco1l.osu.hud.elements.HUDAccuracyCounter +import com.reco1l.osu.hud.elements.HUDComboCounter +import com.reco1l.osu.hud.elements.HUDHealthBar +import com.reco1l.osu.hud.elements.HUDPPCounter +import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.osu.hud.ProgressIndicatorType.Companion.PIE +import com.reco1l.osu.hud.ProgressIndicatorType.Companion.BAR +import com.reco1l.osu.hud.data.HUDLayoutData +import com.reco1l.osu.hud.elements.HUDElement +import com.reco1l.osu.hud.elements.HUDPieSongProgress +import com.reco1l.toolkt.kotlin.fastForEach +import org.anddev.andengine.engine.camera.hud.* +import org.anddev.andengine.entity.IEntity +import ru.nsu.ccfit.zuev.osu.* +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.* +import ru.nsu.ccfit.zuev.osuplus.BuildConfig + +class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : ConstraintContainer() { + + + /** + * The layout data for the HUD. + */ + var layoutData: HUDLayoutData = HUDLayoutData.Default + set(value) { + if (field != value) { + onLayoutDataChange(value) + field = value + } + } + + /** + * Whether the HUD is in edit mode or not. + */ + var isInEditMode = true + set(value) { + if (field != value) { + onEditModeChange(value) + field = value + } + } + + + init { + // The engine we expect the HUD to be a effectively an instance of the AndEngine's HUD class. + // Since we need ConstraintContainer features we set a HUD instance as the parent and we just + // need to reference the parent of this container to set the engine's HUD. + val parent = HUD() + parent.attachChild(this) + parent.registerTouchArea(this) + + relativeSizeAxes = Axes.Both + setSize(1f, 1f) + + onLayoutDataChange(layoutData) + onEditModeChange(isInEditMode) + } + + + override fun getParent(): HUD? { + // Nullable because during initialization the parent is not set yet. + return super.getParent() as? HUD + } + + + private fun onEditModeChange(value: Boolean) { + mChildren?.forEach { (it as? HUDElement)?.isInEditMode = value } + } + + private fun onLayoutDataChange(layoutData: HUDLayoutData) { + mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) + + // First pass: We attach everything so that elements can reference between them in the second + // pass for constraints. + layoutData.elements.forEach { (tag, data) -> + + val element = createElementFromTag(tag) + attachChild(element) + + if (BuildConfig.DEBUG) { + Log.i("GameplayHUD", "Attached element: $tag with data: $data") + } + } + + // Second pass: We apply the constraints. + layoutData.elements.forEach { (tag, data) -> + getElementByTag(tag)!!.onElementDataChange(data) + } + } + + + override fun onManagedUpdate(pSecondsElapsed: Float) { + mChildren?.fastForEach { + (it as? HUDElement)?.onGameplayUpdate(game, stat, pSecondsElapsed) + } + super.onManagedUpdate(pSecondsElapsed) + } + + fun onNoteHit(statistics: StatisticV2) { + mChildren?.fastForEach { + (it as? HUDElement)?.onNoteHit(statistics) + } + } + + fun onBreakStateChange(isBreak: Boolean) { + mChildren?.fastForEach { + (it as? HUDElement)?.onBreakStateChange(isBreak) + } + } + + + fun getElementByTag(tag: String): HUDElement? { + return mChildren?.firstOrNull { it is HUDElement && it.tag == tag } as? HUDElement + } + + companion object { + + /** + * Creates a new HUD element from its tag. + * + * This is a factory method that creates a new instance of a HUD element based on the tag. + * If new HUD elements are added, they should be added here too. + */ + fun createElementFromTag(tag: String): HUDElement { + return when (tag) { + "healthBar" -> HUDHealthBar() + "ppCounter" -> HUDPPCounter() + "scoreCounter" -> HUDScoreCounter() + "comboCounter" -> HUDComboCounter() + "accuracyCounter" -> HUDAccuracyCounter() + "pieSongProgress" -> HUDPieSongProgress() + else -> throw IllegalArgumentException("Unknown tag: $tag") + } + } + + } + +} + + +@IntDef(PIE, BAR) +annotation class ProgressIndicatorType { + companion object { + const val PIE = 0 + const val BAR = 1 + } +} + + diff --git a/src/com/reco1l/osu/hud/data/HUDLayoutData.kt b/src/com/reco1l/osu/hud/data/HUDLayoutData.kt new file mode 100644 index 000000000..a8e239252 --- /dev/null +++ b/src/com/reco1l/osu/hud/data/HUDLayoutData.kt @@ -0,0 +1,82 @@ +package com.reco1l.osu.hud.data + +import com.reco1l.andengine.Anchor +import com.reco1l.framework.math.Vec2 + +data class HUDLayoutData(val elements: Map) { + + + fun hasElement(tag: String): Boolean { + return elements.containsKey(tag) + } + + + companion object { + + /** + * The default layout data for the HUD. + * Based on the default skin layout from osu!stable. + */ + val Default = HUDLayoutData( + mapOf( + "accuracyCounter" to HUDElementData( + scale = 0.6f * 0.96f, + constraintOffset = Vec2(0f, 9f), + constraintTo = "scoreCounter", + anchor = Anchor.BottomRight, + origin = Anchor.TopRight + ), + "comboCounter" to HUDElementData( + scale = 1.28f, + constraintOffset = Vec2(10f, -10f), + anchor = Anchor.BottomLeft, + origin = Anchor.BottomLeft + ), + "pieSongProgress" to HUDElementData( + constraintOffset = Vec2(18f, 0f), + constraintTo = "accuracyCounter", + anchor = Anchor.TopLeft, + origin = Anchor.CenterRight + ), + "healthBar" to HUDElementData(), + "scoreCounter" to HUDElementData( + scale = 0.96f, + constraintOffset = Vec2(-10f, 0f), + anchor = Anchor.TopRight, + origin = Anchor.TopRight + ) + ) + ) + + } + +} + + +data class HUDElementData( + + /** + * The scale applied to the element. + */ + val scale: Float = 1f, + + /** + * The offset of the element from the constraint. + */ + val constraintOffset: Vec2 = Vec2.Zero, + + /** + * The tag of the constraint to which the element is attached. + */ + val constraintTo: String? = null, + + /** + * The anchor of the element. + */ + val anchor: Vec2 = Anchor.TopLeft, + + /** + * The origin of the element. + */ + val origin: Vec2 = Anchor.TopLeft, +) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt new file mode 100644 index 000000000..e6e504a05 --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt @@ -0,0 +1,25 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.osu.playfield.SpriteFont +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import ru.nsu.ccfit.zuev.skins.OsuSkin +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Locale + +class HUDAccuracyCounter : HUDElement(tag = "accuracyCounter") { + + private val sprite = SpriteFont(OsuSkin.get().scorePrefix) + private val format = DecimalFormat("0.00%", DecimalFormatSymbols(Locale.US)) + + init { + sprite.text = "100.00%" + attachChild(sprite) + } + + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + sprite.text = format.format(statistics.accuracy) + } + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/playfield/Counters.kt b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt similarity index 63% rename from src/com/reco1l/osu/playfield/Counters.kt rename to src/com/reco1l/osu/hud/elements/HUDComboCounter.kt index edaad2f54..533153ddc 100644 --- a/src/com/reco1l/osu/playfield/Counters.kt +++ b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt @@ -1,76 +1,16 @@ -package com.reco1l.osu.playfield +package com.reco1l.osu.hud.elements import com.edlplan.framework.easing.* import com.reco1l.andengine.* -import com.reco1l.andengine.container.* import com.reco1l.andengine.modifier.OnModifierFinished +import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.* +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 import ru.nsu.ccfit.zuev.skins.* -import java.text.* -import java.util.* -import kotlin.math.roundToInt -class ScoreCounter : SpriteFont(OsuSkin.get().scorePrefix) { - - private val format = DecimalFormat("00000000", DecimalFormatSymbols(Locale.US)) - - - init { - anchor = Anchor.TopRight - origin = Anchor.TopRight - setScale(0.96f) - - x = -10f - spacing = -OsuSkin.get().scoreOverlap - } - - - fun setScore(value: Int) { - text = format.format(value) - } - -} - -class PPCounter(private val algorithm: DifficultyAlgorithm) : SpriteFont(OsuSkin.get().scorePrefix) { - - init { - anchor = Anchor.TopRight - origin = Anchor.TopRight - setScale(0.6f * 0.96f) - setValue(0.0) - } - - - fun setValue(value: Double) { - text = "${value.roundToInt()}${if (algorithm == DifficultyAlgorithm.droid) "dpp" else "pp"}" - } -} - - -class AccuracyCounter : SpriteFont(OsuSkin.get().scorePrefix) { - - - private val format = DecimalFormat("0.00%", DecimalFormatSymbols(Locale.US)) - - - init { - anchor = Anchor.TopRight - origin = Anchor.TopRight - setScale(0.6f * 0.96f) - setPosition(-17f, 9f) - text = "100.00%" - } - - - fun setAccuracy(value: Float) { - text = format.format(value) - } - -} - - -class ComboCounter : Container() { +class HUDComboCounter : HUDElement(tag = "comboCounter") { private val popOutCount = if (Config.isAnimateComboText()) SpriteFont(OsuSkin.get().comboPrefix).also { @@ -113,14 +53,6 @@ class ComboCounter : Container() { private var current = 0 - init { - anchor = Anchor.BottomLeft - origin = Anchor.BottomLeft - setPosition(10f, -10f) - setScale(1.28f) - } - - fun setCombo(value: Int) { if (current == value) { @@ -157,14 +89,16 @@ class ComboCounter : Container() { } + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + setCombo(statistics.combo) + } + + companion object { const val FONT_HEIGHT_RATIO = 0.625f - const val VERTICAL_OFFSET = 9f - const val BIG_POP_OUT_DURATION = 0.3f - const val SMALL_POP_OUT_DURATION = 0.1f } diff --git a/src/com/reco1l/osu/hud/elements/HUDElement.kt b/src/com/reco1l/osu/hud/elements/HUDElement.kt new file mode 100644 index 000000000..31bf1dfef --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDElement.kt @@ -0,0 +1,153 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.Axes +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.framework.ColorARGB +import com.reco1l.osu.hud.GameplayHUD +import com.reco1l.osu.hud.data.HUDElementData +import com.reco1l.toolkt.kotlin.capitalize +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.ResourceManager +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 + +abstract class HUDElement( + + /** + * The tag of the element. This is used to identify the element in the HUD. + */ + val tag: String + +) : Container() { + + /** + * Whether the element is in edit mode or not. + */ + var isInEditMode = false + set(value) { + if (field != value) { + onEditModeChange(value) + field = value + } + } + + /** + * Returns the tag with a readable manner. + */ + val readableTag: String + get() = tag.replace("([a-z])([A-Z])".toRegex(), "$1 $2").lowercase().capitalize() + + + override fun getParent(): GameplayHUD? { + return super.getParent() as? GameplayHUD + } + + + open fun onEditModeChange(value: Boolean) { + background = if (value) HUDElementUnderlay(this) else null + } + + fun onElementDataChange(data: HUDElementData) { + + parent!!.removeConstraint(this) + + if (data.constraintTo != null) { + parent!!.addConstraint(this, parent!!.getElementByTag(data.constraintTo)!!) + } + + anchor = data.anchor + origin = data.origin + setScale(data.scale) + setPosition(data.constraintOffset.x, data.constraintOffset.y) + } + + + private var initialX = 0f + private var initialY = 0f + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (isInEditMode) { + val parentLocalX = drawX + localX + val parentLocalY = drawY + localY + + if (event.action == TouchEvent.ACTION_DOWN) { + //parent!!.onElementSelected(this) + initialX = parentLocalX + initialY = parentLocalY + return true + } + + if (event.action == TouchEvent.ACTION_MOVE) { + val deltaX = parentLocalX - initialX + val deltaY = parentLocalY - initialY + + setPosition(x + deltaX, y + deltaY) + + initialX = parentLocalX + initialY = parentLocalY + return true + } + + } + return false + } + + + open fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + // Override this method to update the element with the latest gameplay data. + } + + open fun onNoteHit(statistics: StatisticV2) { + // Override this method to handle hit object hits. + } + + open fun onBreakStateChange(isBreak: Boolean) { + // Override this method to handle break state changes. + } + +} + + +class HUDElementUnderlay(private val element: HUDElement) : Container() { + + private val tagText = ExtendedText() + + + init { + tagText.font = ResourceManager.getInstance().getFont("smallFont") + tagText.color = ColorARGB.White + tagText.text = element.readableTag + attachChild(tagText) + + autoSizeAxes = Axes.None + + background = RoundedBox().apply { + color = ColorARGB.Red + alpha = 0.25f + } + } + + override fun onManagedUpdate(pSecondsElapsed: Float) { + + if (element.drawY - drawHeight <= 0f) { + tagText.anchor = Anchor.BottomLeft + tagText.origin = Anchor.TopLeft + } else { + tagText.anchor = Anchor.TopLeft + tagText.origin = Anchor.BottomLeft + } + + // Cancel the scaling of the HUD element so the text is not affected by it, the same goes for the background. + tagText.setScale(1f / element.scaleX, 1f / element.scaleY) + + (background as RoundedBox).cornerRadius = 6f * (1f / element.scaleX) + } + + + + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/playfield/HealthBar.kt b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt similarity index 87% rename from src/com/reco1l/osu/playfield/HealthBar.kt rename to src/com/reco1l/osu/hud/elements/HUDHealthBar.kt index b210061d2..54ec57637 100644 --- a/src/com/reco1l/osu/playfield/HealthBar.kt +++ b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt @@ -1,34 +1,28 @@ -package com.reco1l.osu.playfield +package com.reco1l.osu.hud.elements import com.edlplan.framework.easing.* import com.reco1l.andengine.* import com.reco1l.andengine.Anchor -import com.reco1l.andengine.container.* import com.reco1l.andengine.shape.* import com.reco1l.andengine.sprite.* import com.reco1l.andengine.texture.* import com.reco1l.framework.* import org.anddev.andengine.opengl.texture.region.* import ru.nsu.ccfit.zuev.osu.* +import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* import ru.nsu.ccfit.zuev.skins.* - -class HealthBar(private val statistics: StatisticV2) : Container() { - +class HUDHealthBar : HUDElement(tag = "healthBar") { private val fill: AnimatedSprite - private val fillClear: Box private val marker: ExtendedSprite - private val explode: ExtendedSprite private val markerNormalTexture: TextureRegion? - private val markerDangerTexture: TextureRegion? - private val markerSuperDangerTexture: TextureRegion? private val isNewStyle: Boolean @@ -96,9 +90,9 @@ class HealthBar(private val statistics: StatisticV2) : Container() { } - override fun onManagedUpdate(pSecondsElapsed: Float) { + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { - fillClear.width = Interpolation.floatAt(pSecondsElapsed.coerceIn(0f, 0.2f), fillClear.drawWidth, (1f - statistics.hp) * fill.drawWidth, 0f, 0.2f, Easing.OutQuint) + fillClear.width = Interpolation.floatAt(secondsElapsed.coerceIn(0f, 0.2f), fillClear.drawWidth, (1f - statistics.hp) * fill.drawWidth, 0f, 0.2f, Easing.OutQuint) marker.x = fill.x + fill.drawWidth - fillClear.drawWidth marker.y = fill.y + (if (isNewStyle) fill.drawHeight / 2 else 0f) @@ -128,14 +122,20 @@ class HealthBar(private val statistics: StatisticV2) : Container() { } } + } + + override fun onNoteHit(statistics: StatisticV2) { + flash(statistics.hp) + } - super.onManagedUpdate(pSecondsElapsed) + override fun onBreakStateChange(isBreak: Boolean) { + isVisible = !isBreak } - fun flash() { + private fun flash(hp: Float) { - val isEpic = statistics.hp >= EPIC_CUTOFF + val isEpic = hp >= EPIC_CUTOFF bulge() diff --git a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt new file mode 100644 index 000000000..99f4d680b --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt @@ -0,0 +1,27 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.osu.playfield.SpriteFont +import ru.nsu.ccfit.zuev.osu.Config +import ru.nsu.ccfit.zuev.osu.DifficultyAlgorithm +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import ru.nsu.ccfit.zuev.skins.OsuSkin +import kotlin.math.roundToInt + +class HUDPPCounter : HUDElement(tag = "ppCounter") { + + private val sprite = SpriteFont(OsuSkin.get().scorePrefix) + + init { + setValue(0.0) + attachChild(sprite) + } + + fun setValue(value: Double) { + sprite.text = "${value.roundToInt()}${if (Config.getDifficultyAlgorithm() == DifficultyAlgorithm.droid) "dpp" else "pp"}" + } + + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + // TODO: Update PP + } +} \ No newline at end of file diff --git a/src/com/reco1l/osu/playfield/CircularSongProgress.kt b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt similarity index 74% rename from src/com/reco1l/osu/playfield/CircularSongProgress.kt rename to src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt index 1da0373e3..2a21f4076 100644 --- a/src/com/reco1l/osu/playfield/CircularSongProgress.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt @@ -1,12 +1,13 @@ -package com.reco1l.osu.playfield +package com.reco1l.osu.hud.elements import com.reco1l.andengine.* -import com.reco1l.andengine.container.* import com.reco1l.andengine.shape.* import com.reco1l.framework.* +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 -class CircularSongProgress : Container() { +class HUDPieSongProgress : HUDElement(tag = "pieSongProgress") { override var autoSizeAxes = Axes.Both @@ -80,4 +81,12 @@ class CircularSongProgress : Container() { } + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + if (game.elapsedTime < game.firstObjectStartTime) { + setProgress((game.elapsedTime - game.initialElapsedTime) / (game.firstObjectStartTime - game.initialElapsedTime), true) + } else { + setProgress((game.elapsedTime - game.firstObjectStartTime) / (game.lastObjectEndTime - game.firstObjectStartTime), false) + } + } + } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt b/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt new file mode 100644 index 000000000..5c0ab03b2 --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt @@ -0,0 +1,26 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.osu.playfield.SpriteFont +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import ru.nsu.ccfit.zuev.skins.OsuSkin +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Locale + +class HUDScoreCounter : HUDElement(tag = "scoreCounter") { + + private val sprite = SpriteFont(OsuSkin.get().scorePrefix) + private val format = DecimalFormat("00000000", DecimalFormatSymbols(Locale.US)) + + init { + sprite.spacing = -OsuSkin.get().scoreOverlap + sprite.text = format.format(0) + attachChild(sprite) + } + + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + sprite.text = format.format(statistics.totalScoreWithMultiplier) + } + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/playfield/GameplayHUD.kt b/src/com/reco1l/osu/playfield/GameplayHUD.kt deleted file mode 100644 index 886729d2c..000000000 --- a/src/com/reco1l/osu/playfield/GameplayHUD.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.reco1l.osu.playfield - -import androidx.annotation.* -import com.reco1l.osu.playfield.ProgressIndicatorType.Companion.PIE -import com.reco1l.osu.playfield.ProgressIndicatorType.Companion.BAR -import org.anddev.andengine.engine.camera.hud.* -import ru.nsu.ccfit.zuev.osu.* -import ru.nsu.ccfit.zuev.osu.game.GameScene -import ru.nsu.ccfit.zuev.osu.scoring.* - -class GameplayHUD(private val stat: StatisticV2, private val game: GameScene, private val withStatistics: Boolean) : HUD() { - - - private val healthBar: HealthBar? - - private val ppCounter: PPCounter? - - private val scoreCounter: ScoreCounter? - - private val comboCounter: ComboCounter? - - private val pieSongProgress: CircularSongProgress? - - private val accuracyCounter: AccuracyCounter? - - - init { - if (withStatistics) { - healthBar = HealthBar(stat) - attachChild(healthBar) - - scoreCounter = ScoreCounter() - attachChild(scoreCounter) - - comboCounter = ComboCounter() - attachChild(comboCounter) - - accuracyCounter = AccuracyCounter() - attachChild(accuracyCounter) - - if (Config.getProgressIndicatorType() == PIE) { - pieSongProgress = CircularSongProgress() - attachChild(pieSongProgress) - } else { - pieSongProgress = null - } - - if (Config.isDisplayRealTimePPCounter()) { - ppCounter = PPCounter(Config.getDifficultyAlgorithm()) - attachChild(ppCounter) - } else { - ppCounter = null - } - - scoreCounter.setScore(0) - accuracyCounter.setAccuracy(100f) - - } else { - healthBar = null - ppCounter = null - scoreCounter = null - comboCounter = null - accuracyCounter = null - pieSongProgress = null - } - - } - - - override fun onManagedUpdate(pSecondsElapsed: Float) { - - if (withStatistics) { - comboCounter!!.setCombo(stat.combo) - scoreCounter!!.setScore(stat.totalScoreWithMultiplier) - - accuracyCounter!!.setAccuracy(stat.accuracy) - accuracyCounter.y = 9f + scoreCounter.y + scoreCounter.drawHeight - - if (Config.getProgressIndicatorType() == PIE) { - pieSongProgress!!.x = accuracyCounter.x - accuracyCounter.widthScaled - 18f - pieSongProgress.y = accuracyCounter.y + accuracyCounter.heightScaled / 2f - - if (Config.isDisplayRealTimePPCounter()) { - ppCounter!!.x = pieSongProgress.x - pieSongProgress.widthScaled - 18f - ppCounter.y = pieSongProgress.drawY - } - - if (game.elapsedTime < game.firstObjectStartTime) { - pieSongProgress.setProgress((game.elapsedTime - game.initialElapsedTime) / (game.firstObjectStartTime - game.initialElapsedTime), true) - } else { - pieSongProgress.setProgress((game.elapsedTime - game.firstObjectStartTime) / (game.lastObjectEndTime - game.firstObjectStartTime), false) - } - } else if (Config.isDisplayRealTimePPCounter()) { - ppCounter!!.x = accuracyCounter.x - accuracyCounter.widthScaled - 18f - ppCounter.y = accuracyCounter.drawY - } - } - - super.onManagedUpdate(pSecondsElapsed) - } - - - fun setHealthBarVisibility(visible: Boolean) { - healthBar?.isVisible = visible - } - - fun flashHealthBar() { - healthBar?.flash() - } - - fun setPPCounterValue(pp: Double) { - ppCounter?.setValue(pp) - } - -} - - -@IntDef(PIE, BAR) -annotation class ProgressIndicatorType { - companion object { - const val PIE = 0 - const val BAR = 1 - } -} - - diff --git a/src/ru/nsu/ccfit/zuev/osu/Config.java b/src/ru/nsu/ccfit/zuev/osu/Config.java index 90375d095..765a00d71 100644 --- a/src/ru/nsu/ccfit/zuev/osu/Config.java +++ b/src/ru/nsu/ccfit/zuev/osu/Config.java @@ -18,7 +18,7 @@ import java.util.UUID; import com.reco1l.osu.multiplayer.Multiplayer; -import com.reco1l.osu.playfield.ProgressIndicatorType; +import com.reco1l.osu.hud.ProgressIndicatorType; import net.margaritov.preference.colorpicker.ColorPickerPreference; diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index f26aee5b5..ad90c29b7 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -31,8 +31,8 @@ import com.reco1l.andengine.sprite.VideoSprite; import com.reco1l.andengine.ExtendedScene; import com.reco1l.osu.hitobjects.FollowPointConnection; -import com.reco1l.osu.playfield.GameplayHUD; -import com.reco1l.osu.playfield.ProgressIndicatorType; +import com.reco1l.osu.hud.GameplayHUD; +import com.reco1l.osu.hud.ProgressIndicatorType; import com.reco1l.osu.hitobjects.SliderTickSprite; import com.reco1l.osu.ui.BlockAreaFragment; import com.reco1l.osu.ui.entity.GameplayLeaderboard; @@ -775,7 +775,7 @@ private void prepareScene() { } counterTexts.clear(); - hud = new GameplayHUD(stat, this, !Config.isHideInGameUI()); + hud = new GameplayHUD(stat, this); var counterTextFont = ResourceManager.getInstance().getFont("smallFont"); @@ -897,7 +897,7 @@ protected void onManagedDraw(GL10 pGL, Camera pCamera) { if (!Config.isHideInGameUI()) { if (Config.getProgressIndicatorType() == ProgressIndicatorType.BAR) { - linearSongProgress = new LinearSongProgress(hud, lastObjectEndTime, firstObjectStartTime, new PointF(0, Config.getRES_HEIGHT() - 7), Config.getRES_WIDTH(), 7); + linearSongProgress = new LinearSongProgress(hud.getParent(), lastObjectEndTime, firstObjectStartTime, new PointF(0, Config.getRES_HEIGHT() - 7), Config.getRES_WIDTH(), 7); linearSongProgress.setProgressRectColor(new RGBColor(153f / 255f, 204f / 255f, 51f / 255f)); linearSongProgress.setProgressRectAlpha(0.4f); linearSongProgress.setInitialPassedTime(initialElapsedTime); @@ -905,7 +905,7 @@ protected void onManagedDraw(GL10 pGL, Camera pCamera) { } if (Config.getErrorMeter() == 1 || (Config.getErrorMeter() == 2 && replaying)) { - hitErrorMeter = new HitErrorMeter(hud, new PointF(Config.getRES_WIDTH() / 2f, Config.getRES_HEIGHT() - 20), 12, hitWindow); + hitErrorMeter = new HitErrorMeter(hud.getParent(), new PointF(Config.getRES_WIDTH() / 2f, Config.getRES_HEIGHT() - 20), 12, hitWindow); } skipBtn = null; @@ -1022,7 +1022,7 @@ public void start() { engine.setScene(scene); scene.registerUpdateHandler(this); - engine.getCamera().setHUD(hud); + engine.getCamera().setHUD(hud.getParent()); blockAreaFragment = new BlockAreaFragment(); blockAreaFragment.show(false); @@ -1212,7 +1212,7 @@ public void onUpdate(final float pSecondsElapsed) { if (Multiplayer.isConnected()) RoomScene.INSTANCE.getChat().show(); - hud.setHealthBarVisibility(false); + hud.onBreakStateChange(true); breakPeriods.poll(); } } @@ -1223,7 +1223,7 @@ public void onUpdate(final float pSecondsElapsed) { RoomScene.INSTANCE.getChat().dismiss(); gameStarted = true; - hud.setHealthBarVisibility(true); + hud.onBreakStateChange(false); if(GameHelper.isFlashLight()){ flashlightSprite.onBreak(false); @@ -1889,7 +1889,7 @@ public void onCircleHit(int id, final float acc, final PointF pos, createBurstEffect(pos, color); createHitEffect(pos, scoreName, color); - hud.flashHealthBar(); + hud.onNoteHit(stat); } public void onSliderReverse(PointF pos, float ang, RGBColor color) { @@ -1966,7 +1966,7 @@ public void onSliderHit(int id, final int score, final PointF judgementPos, fina createHitEffect(judgementPos, scoreName, color); - hud.flashHealthBar(); + hud.onNoteHit(stat); } @@ -2021,7 +2021,7 @@ public void onSpinnerHit(int id, final int score, final boolean endCombo, int to createHitEffect(pos, scoreName, null); - hud.flashHealthBar(); + hud.onNoteHit(stat); } private void stopLoopingSamples() { @@ -2247,7 +2247,7 @@ public void pause() { scene.setIgnoreUpdate(true); final PauseMenu menu = new PauseMenu(engine, this, false); - hud.setChildScene(menu.getScene(), false, true, true); + hud.getParent().setChildScene(menu.getScene(), false, true, true); } public void gameover() { @@ -2382,7 +2382,7 @@ public void onUpdate(float pSecondsElapsed) { engine.unregisterUpdateHandler(this); PauseMenu menu = new PauseMenu(engine, GameScene.this, true); - hud.setChildScene(menu.getScene(), false, true, true); + hud.getParent().setChildScene(menu.getScene(), false, true, true); } } @@ -2403,7 +2403,7 @@ public void resume() { blockAreaFragment.show(); scene.setIgnoreUpdate(false); - hud.getChildScene().back(); + hud.getParent().getChildScene().back(); paused = false; if (stat.getHp() <= 0 && !stat.getMod().contains(GameMod.MOD_NOFAIL)) { @@ -2772,7 +2772,8 @@ private void updatePPCounter(int objectId) { case standard -> getStandardPPAt(objectId); }; - hud.setPPCounterValue(!Double.isNaN(pp) ? pp : 0); + // TODO: Do PP counter stuff here + //hud.setPPCounterValue(!Double.isNaN(pp) ? pp : 0); } private double getDroidPPAt(int objectId) { From 9a024fcf649a867d20610c624f877d1f9279111b Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 15:05:23 -0300 Subject: [PATCH 06/85] Change TextureFont text immediately --- src/com/reco1l/andengine/text/TextureFont.kt | 23 ++++---------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/com/reco1l/andengine/text/TextureFont.kt b/src/com/reco1l/andengine/text/TextureFont.kt index 79d3cc467..2e9a40a2f 100644 --- a/src/com/reco1l/andengine/text/TextureFont.kt +++ b/src/com/reco1l/andengine/text/TextureFont.kt @@ -21,7 +21,7 @@ open class TextureFont(private val characters: MutableMap) set(value) { if (field != value) { field = value - isTextDirty = true + onUpdateText() } } @@ -32,7 +32,7 @@ open class TextureFont(private val characters: MutableMap) set(value) { if (field != value) { field = value - isTextDirty = true + onUpdateText() } } @@ -43,7 +43,7 @@ open class TextureFont(private val characters: MutableMap) set(value) { if (field != value) { field = value - isTextDirty = true + onUpdateText() } } @@ -54,7 +54,7 @@ open class TextureFont(private val characters: MutableMap) set(value) { if (field != value) { field = value - isTextDirty = true + onUpdateText() } } @@ -62,29 +62,14 @@ open class TextureFont(private val characters: MutableMap) private val textureRegions = mutableListOf() - private var isTextDirty = true - - fun setTextureScale(scale: Float) { textureScaleX = scale textureScaleY = scale } - override fun onManagedDraw(pGL: GL10, pCamera: Camera) { - - if (isTextDirty) { - onUpdateText() - } - - super.onManagedDraw(pGL, pCamera) - } - - private fun onUpdateText() { - isTextDirty = false - contentWidth = 0f contentHeight = 0f From 7d0462fe71881e0038f71e8395098c36683c21b2 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 15:05:33 -0300 Subject: [PATCH 07/85] Introduce Line --- src/com/reco1l/andengine/shape/Line.kt | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/com/reco1l/andengine/shape/Line.kt diff --git a/src/com/reco1l/andengine/shape/Line.kt b/src/com/reco1l/andengine/shape/Line.kt new file mode 100644 index 000000000..db99e3811 --- /dev/null +++ b/src/com/reco1l/andengine/shape/Line.kt @@ -0,0 +1,72 @@ +package com.reco1l.andengine.shape + +import com.reco1l.andengine.* +import com.reco1l.framework.math.Vec2 +import org.anddev.andengine.engine.camera.* +import org.anddev.andengine.opengl.util.GLHelper +import org.anddev.andengine.opengl.vertex.VertexBuffer +import javax.microedition.khronos.opengles.* +import javax.microedition.khronos.opengles.GL10.* + +/** + * A rectangle shape based on [ExtendedEntity]. + */ +open class Line : ExtendedEntity(vertexBuffer = LineVertexBuffer()) { + + /** + * The width of the line. + */ + var lineWidth = 1f + + var fromPoint = Vec2.Zero + set(value) { + if (field != value) { + field = value + onUpdateVertexBuffer() + } + } + + var toPoint = Vec2.Zero + set(value) { + if (field != value) { + field = value + onUpdateVertexBuffer() + } + } + + + override fun onInitDraw(pGL: GL10) { + super.onInitDraw(pGL) + + GLHelper.disableCulling(pGL) + GLHelper.disableTextures(pGL) + GLHelper.disableTexCoordArray(pGL) + } + + + override fun onUpdateVertexBuffer() { + (vertexBuffer as LineVertexBuffer).update(fromPoint, toPoint) + } + + override fun drawVertices(gl: GL10, camera: Camera) { + gl.glLineWidth(lineWidth) + gl.glDrawArrays(GL_LINES, 0, 2) + } + + + class LineVertexBuffer : VertexBuffer(2 * 2, GL11.GL_STATIC_DRAW, false) { + + fun update(fromPoint: Vec2, toPoint: Vec2) { + floatBuffer.apply { + position(0) + put(fromPoint.x) + put(fromPoint.y) + put(toPoint.x) + put(toPoint.y) + } + setHardwareBufferNeedsUpdate() + } + + } + +} From ce0803728dd150cfc05a07b4852edfa8672fc7e0 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 16:33:06 -0300 Subject: [PATCH 08/85] Implement selection and auto anchor calculation system --- src/com/reco1l/andengine/shape/Line.kt | 9 +- src/com/reco1l/osu/hud/GameplayHUD.kt | 188 ++++++++++++------ src/com/reco1l/osu/hud/data/HUDLayoutData.kt | 82 -------- src/com/reco1l/osu/hud/data/HUDSkinData.kt | 74 +++++++ .../osu/hud/elements/HUDAccuracyCounter.kt | 3 +- .../osu/hud/elements/HUDComboCounter.kt | 20 +- src/com/reco1l/osu/hud/elements/HUDElement.kt | 152 ++++++++++---- .../reco1l/osu/hud/elements/HUDHealthBar.kt | 4 +- .../reco1l/osu/hud/elements/HUDPPCounter.kt | 3 +- .../osu/hud/elements/HUDPieSongProgress.kt | 16 +- .../osu/hud/elements/HUDScoreCounter.kt | 4 +- src/ru/nsu/ccfit/zuev/osu/Config.java | 2 +- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 2 +- 13 files changed, 349 insertions(+), 210 deletions(-) delete mode 100644 src/com/reco1l/osu/hud/data/HUDLayoutData.kt create mode 100644 src/com/reco1l/osu/hud/data/HUDSkinData.kt diff --git a/src/com/reco1l/andengine/shape/Line.kt b/src/com/reco1l/andengine/shape/Line.kt index db99e3811..d58274708 100644 --- a/src/com/reco1l/andengine/shape/Line.kt +++ b/src/com/reco1l/andengine/shape/Line.kt @@ -22,7 +22,7 @@ open class Line : ExtendedEntity(vertexBuffer = LineVertexBuffer()) { set(value) { if (field != value) { field = value - onUpdateVertexBuffer() + updateVertexBuffer() } } @@ -30,7 +30,7 @@ open class Line : ExtendedEntity(vertexBuffer = LineVertexBuffer()) { set(value) { if (field != value) { field = value - onUpdateVertexBuffer() + updateVertexBuffer() } } @@ -41,6 +41,7 @@ open class Line : ExtendedEntity(vertexBuffer = LineVertexBuffer()) { GLHelper.disableCulling(pGL) GLHelper.disableTextures(pGL) GLHelper.disableTexCoordArray(pGL) + pGL.glLineWidth(lineWidth) } @@ -49,12 +50,11 @@ open class Line : ExtendedEntity(vertexBuffer = LineVertexBuffer()) { } override fun drawVertices(gl: GL10, camera: Camera) { - gl.glLineWidth(lineWidth) gl.glDrawArrays(GL_LINES, 0, 2) } - class LineVertexBuffer : VertexBuffer(2 * 2, GL11.GL_STATIC_DRAW, false) { + class LineVertexBuffer : VertexBuffer(2 * 2, GL11.GL_STATIC_DRAW, true) { fun update(fromPoint: Vec2, toPoint: Vec2) { floatBuffer.apply { @@ -63,6 +63,7 @@ open class Line : ExtendedEntity(vertexBuffer = LineVertexBuffer()) { put(fromPoint.y) put(toPoint.x) put(toPoint.y) + position(0) } setHardwareBufferNeedsUpdate() } diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index ad800cc08..c80e82d52 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -1,37 +1,36 @@ package com.reco1l.osu.hud -import android.util.Log -import androidx.annotation.* +import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes -import com.reco1l.andengine.container.ConstraintContainer +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.shape.Line +import com.reco1l.framework.ColorARGB +import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.hud.data.HUDSkinData import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDComboCounter -import com.reco1l.osu.hud.elements.HUDHealthBar -import com.reco1l.osu.hud.elements.HUDPPCounter -import com.reco1l.osu.hud.elements.HUDScoreCounter -import com.reco1l.osu.hud.ProgressIndicatorType.Companion.PIE -import com.reco1l.osu.hud.ProgressIndicatorType.Companion.BAR -import com.reco1l.osu.hud.data.HUDLayoutData import com.reco1l.osu.hud.elements.HUDElement import com.reco1l.osu.hud.elements.HUDPieSongProgress +import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.osu.hud.elements.create import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.hud.* import org.anddev.andengine.entity.IEntity -import ru.nsu.ccfit.zuev.osu.* +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.GlobalManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* -import ru.nsu.ccfit.zuev.osuplus.BuildConfig -class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : ConstraintContainer() { +class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : Container() { /** * The layout data for the HUD. */ - var layoutData: HUDLayoutData = HUDLayoutData.Default + var skinData: HUDSkinData = HUDSkinData.Default set(value) { if (field != value) { - onLayoutDataChange(value) + onSkinDataChange(value) field = value } } @@ -47,51 +46,125 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : } } + var selected: HUDElement? = null + set(value) { + if (field != value) { + field = value + mChildren?.fastForEach { + (it as? HUDElement)?.isSelected = it == value + } + } + } + init { // The engine we expect the HUD to be a effectively an instance of the AndEngine's HUD class. - // Since we need ConstraintContainer features we set a HUD instance as the parent and we just - // need to reference the parent of this container to set the engine's HUD. + // Since we need Container features we set a HUD instance as the parent and we just need to + // reference the parent of this container to set the engine's HUD. val parent = HUD() parent.attachChild(this) parent.registerTouchArea(this) + parent.camera = GlobalManager.getInstance().engine.camera relativeSizeAxes = Axes.Both setSize(1f, 1f) - onLayoutDataChange(layoutData) + onSkinDataChange(skinData) onEditModeChange(isInEditMode) } - override fun getParent(): HUD? { - // Nullable because during initialization the parent is not set yet. - return super.getParent() as? HUD - } - - private fun onEditModeChange(value: Boolean) { mChildren?.forEach { (it as? HUDElement)?.isInEditMode = value } } - private fun onLayoutDataChange(layoutData: HUDLayoutData) { - mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) + private fun onSkinDataChange(layoutData: HUDSkinData) { - // First pass: We attach everything so that elements can reference between them in the second - // pass for constraints. - layoutData.elements.forEach { (tag, data) -> + mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) - val element = createElementFromTag(tag) + // First pass: We attach everything so that elements can reference between them when + // applying default layout. + layoutData.elements.forEach { data -> + val element = data.type.create() + element.elementData = data attachChild(element) + } + + applyDefaultLayout() + + // Second pass: Apply custom element data that will override the default layout if any. + mChildren?.forEach { (it as? HUDElement)?.onApplyElementSkinData() } + + addAnchorNodes() + } + + private fun applyDefaultLayout() { + // Default layout is hardcoded to keep the original layout of the game before the HUD + // editor was implemented. As well there's no other way since the original layout was + // using cross references between elements that are not possible to be set in the editor. + + val scoreCounter = getFirstOf() + scoreCounter?.anchor = Anchor.TopRight + scoreCounter?.origin = Anchor.TopRight + scoreCounter?.setScale(0.96f) + scoreCounter?.x = -10f + + val accuracyCounter = getFirstOf() + accuracyCounter?.anchor = Anchor.TopRight + accuracyCounter?.origin = Anchor.TopRight + accuracyCounter?.setScale(0.6f * 0.96f) + accuracyCounter?.setPosition(-17f, 9f) + + if (scoreCounter != null && accuracyCounter != null) { + accuracyCounter.y += scoreCounter.y + scoreCounter.drawHeight + } + + val pieSongProgress = getFirstOf() + pieSongProgress?.anchor = Anchor.TopRight + pieSongProgress?.origin = Anchor.CenterRight - if (BuildConfig.DEBUG) { - Log.i("GameplayHUD", "Attached element: $tag with data: $data") + if (pieSongProgress != null && accuracyCounter != null) { + pieSongProgress.y = accuracyCounter.y + accuracyCounter.heightScaled / 2f + pieSongProgress.x = accuracyCounter.x - accuracyCounter.widthScaled - 18f + } + + val comboCounter = getFirstOf() + comboCounter?.anchor = Anchor.BottomLeft + comboCounter?.origin = Anchor.BottomLeft + comboCounter?.setPosition(10f, -10f) + comboCounter?.setScale(1.28f) + } + + private fun addAnchorNodes() { + + fun addAnchorNodeLine(element: HUDElement) { + + val pointOnParent = Vec2( + drawWidth * element.anchor.x, + drawHeight * element.anchor.y + ) + + val pointFromChild = Vec2( + element.drawX + element.drawWidth * element.origin.x, + element.drawY + element.drawHeight * element.origin.y + ) + + element.nodeLine = Line().apply { + fromPoint = pointFromChild + toPoint = pointOnParent + color = ColorARGB(0xFFF27272) + lineWidth = 10f + isVisible = false } + + attachChild(element.nodeLine!!) } - // Second pass: We apply the constraints. - layoutData.elements.forEach { (tag, data) -> - getElementByTag(tag)!!.onElementDataChange(data) + mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) + mChildren?.toList()?.forEach { + if (it is HUDElement) { + addAnchorNodeLine(it) + } } } @@ -103,6 +176,16 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : super.onManagedUpdate(pSecondsElapsed) } + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (super.onAreaTouched(event, localX, localY)) { + return true + } + selected = null + return false + } + + + //region Gameplay Events fun onNoteHit(statistics: StatisticV2) { mChildren?.fastForEach { (it as? HUDElement)?.onNoteHit(statistics) @@ -114,42 +197,17 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : (it as? HUDElement)?.onBreakStateChange(isBreak) } } + //endregion - fun getElementByTag(tag: String): HUDElement? { - return mChildren?.firstOrNull { it is HUDElement && it.tag == tag } as? HUDElement - } - - companion object { - - /** - * Creates a new HUD element from its tag. - * - * This is a factory method that creates a new instance of a HUD element based on the tag. - * If new HUD elements are added, they should be added here too. - */ - fun createElementFromTag(tag: String): HUDElement { - return when (tag) { - "healthBar" -> HUDHealthBar() - "ppCounter" -> HUDPPCounter() - "scoreCounter" -> HUDScoreCounter() - "comboCounter" -> HUDComboCounter() - "accuracyCounter" -> HUDAccuracyCounter() - "pieSongProgress" -> HUDPieSongProgress() - else -> throw IllegalArgumentException("Unknown tag: $tag") - } - } - + private inline fun getFirstOf() : T? { + return mChildren?.firstOrNull { it is T } as? T } -} - -@IntDef(PIE, BAR) -annotation class ProgressIndicatorType { - companion object { - const val PIE = 0 - const val BAR = 1 + override fun getParent(): HUD? { + // Nullable because during initialization the parent is not set yet. + return super.getParent() as? HUD } } diff --git a/src/com/reco1l/osu/hud/data/HUDLayoutData.kt b/src/com/reco1l/osu/hud/data/HUDLayoutData.kt deleted file mode 100644 index a8e239252..000000000 --- a/src/com/reco1l/osu/hud/data/HUDLayoutData.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.reco1l.osu.hud.data - -import com.reco1l.andengine.Anchor -import com.reco1l.framework.math.Vec2 - -data class HUDLayoutData(val elements: Map) { - - - fun hasElement(tag: String): Boolean { - return elements.containsKey(tag) - } - - - companion object { - - /** - * The default layout data for the HUD. - * Based on the default skin layout from osu!stable. - */ - val Default = HUDLayoutData( - mapOf( - "accuracyCounter" to HUDElementData( - scale = 0.6f * 0.96f, - constraintOffset = Vec2(0f, 9f), - constraintTo = "scoreCounter", - anchor = Anchor.BottomRight, - origin = Anchor.TopRight - ), - "comboCounter" to HUDElementData( - scale = 1.28f, - constraintOffset = Vec2(10f, -10f), - anchor = Anchor.BottomLeft, - origin = Anchor.BottomLeft - ), - "pieSongProgress" to HUDElementData( - constraintOffset = Vec2(18f, 0f), - constraintTo = "accuracyCounter", - anchor = Anchor.TopLeft, - origin = Anchor.CenterRight - ), - "healthBar" to HUDElementData(), - "scoreCounter" to HUDElementData( - scale = 0.96f, - constraintOffset = Vec2(-10f, 0f), - anchor = Anchor.TopRight, - origin = Anchor.TopRight - ) - ) - ) - - } - -} - - -data class HUDElementData( - - /** - * The scale applied to the element. - */ - val scale: Float = 1f, - - /** - * The offset of the element from the constraint. - */ - val constraintOffset: Vec2 = Vec2.Zero, - - /** - * The tag of the constraint to which the element is attached. - */ - val constraintTo: String? = null, - - /** - * The anchor of the element. - */ - val anchor: Vec2 = Anchor.TopLeft, - - /** - * The origin of the element. - */ - val origin: Vec2 = Anchor.TopLeft, -) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/data/HUDSkinData.kt b/src/com/reco1l/osu/hud/data/HUDSkinData.kt new file mode 100644 index 000000000..7d45e8d76 --- /dev/null +++ b/src/com/reco1l/osu/hud/data/HUDSkinData.kt @@ -0,0 +1,74 @@ +package com.reco1l.osu.hud.data + +import com.reco1l.andengine.Anchor +import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.hud.elements.HUDAccuracyCounter +import com.reco1l.osu.hud.elements.HUDComboCounter +import com.reco1l.osu.hud.elements.HUDElement +import com.reco1l.osu.hud.elements.HUDHealthBar +import com.reco1l.osu.hud.elements.HUDPieSongProgress +import com.reco1l.osu.hud.elements.HUDScoreCounter +import kotlin.reflect.KClass + +data class HUDSkinData(val elements: List) { + + companion object { + + /** + * The default layout data for the HUD. + * Based on the default skin layout from osu!stable. + */ + val Default = HUDSkinData( + listOf( + HUDElementSkinData(type = HUDAccuracyCounter::class), + HUDElementSkinData(type = HUDComboCounter::class), + HUDElementSkinData(type = HUDPieSongProgress::class), + HUDElementSkinData(type = HUDHealthBar::class), + HUDElementSkinData(type = HUDScoreCounter::class) + ) + ) + + } + +} + +data class HUDElementSkinData( + + /** + * The type of the element. + */ + val type: KClass, + + /** + * The layout of the element. + */ + val layout: HUDElementLayoutData? = null, + + /** + * The set of settings for the element. + */ + val settings: Map = mapOf(), +) + +data class HUDElementLayoutData( + + /** + * The offset of the element from the constraint. + */ + val position: Vec2 = Vec2.Zero, + + /** + * The anchor of the element. + */ + val anchor: Vec2 = Anchor.TopLeft, + + /** + * The origin of the element. + */ + val origin: Vec2 = Anchor.TopLeft, + + /** + * The scale applied to the element. + */ + val scale: Float = 1f, +) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt index e6e504a05..49a6cbfa9 100644 --- a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt @@ -8,7 +8,7 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale -class HUDAccuracyCounter : HUDElement(tag = "accuracyCounter") { +class HUDAccuracyCounter : HUDElement() { private val sprite = SpriteFont(OsuSkin.get().scorePrefix) private val format = DecimalFormat("0.00%", DecimalFormatSymbols(Locale.US)) @@ -16,6 +16,7 @@ class HUDAccuracyCounter : HUDElement(tag = "accuracyCounter") { init { sprite.text = "100.00%" attachChild(sprite) + onMeasureContentSize() } override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { diff --git a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt index 533153ddc..9e67d608c 100644 --- a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt @@ -10,7 +10,7 @@ import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 import ru.nsu.ccfit.zuev.skins.* -class HUDComboCounter : HUDElement(tag = "comboCounter") { +class HUDComboCounter : HUDElement() { private val popOutCount = if (Config.isAnimateComboText()) SpriteFont(OsuSkin.get().comboPrefix).also { @@ -19,13 +19,10 @@ class HUDComboCounter : HUDElement(tag = "comboCounter") { it.text = "0x" it.anchor = Anchor.BottomLeft it.origin = Anchor.BottomLeft + it.spacing = -OsuSkin.get().comboOverlap // In stable, the bigger pop out scales a bit to the left it.translationX = -3f - it.translationY = -(FONT_HEIGHT_RATIO * it.drawHeight + VERTICAL_OFFSET) - - it.y = -(1 - FONT_HEIGHT_RATIO) * it.drawHeight + VERTICAL_OFFSET - it.spacing = -OsuSkin.get().comboOverlap attachChild(it) @@ -36,10 +33,6 @@ class HUDComboCounter : HUDElement(tag = "comboCounter") { it.text = "0x" it.anchor = Anchor.BottomLeft it.origin = Anchor.BottomLeft - - it.translationY = -(FONT_HEIGHT_RATIO * it.drawHeight + VERTICAL_OFFSET) - - it.y = -(1 - FONT_HEIGHT_RATIO) * it.drawHeight + VERTICAL_OFFSET it.spacing = -OsuSkin.get().comboOverlap attachChild(it, 0) @@ -53,6 +46,11 @@ class HUDComboCounter : HUDElement(tag = "comboCounter") { private var current = 0 + init { + onMeasureContentSize() + } + + fun setCombo(value: Int) { if (current == value) { @@ -95,12 +93,8 @@ class HUDComboCounter : HUDElement(tag = "comboCounter") { companion object { - - const val FONT_HEIGHT_RATIO = 0.625f - const val VERTICAL_OFFSET = 9f const val BIG_POP_OUT_DURATION = 0.3f const val SMALL_POP_OUT_DURATION = 0.1f - } } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDElement.kt b/src/com/reco1l/osu/hud/elements/HUDElement.kt index 31bf1dfef..add8dc01b 100644 --- a/src/com/reco1l/osu/hud/elements/HUDElement.kt +++ b/src/com/reco1l/osu/hud/elements/HUDElement.kt @@ -3,25 +3,38 @@ package com.reco1l.osu.hud.elements import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes import com.reco1l.andengine.container.Container +import com.reco1l.andengine.shape.Line import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB +import com.reco1l.framework.math.Vec2 import com.reco1l.osu.hud.GameplayHUD -import com.reco1l.osu.hud.data.HUDElementData -import com.reco1l.toolkt.kotlin.capitalize +import com.reco1l.osu.hud.data.HUDElementSkinData import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import kotlin.math.abs +import kotlin.reflect.KClass -abstract class HUDElement( + +fun KClass.create(): T { + return constructors.first().call() +} + +abstract class HUDElement : Container() { /** - * The tag of the element. This is used to identify the element in the HUD. + * The HUD element data for this element. This property can only be set once. */ - val tag: String - -) : Container() { + var elementData: HUDElementSkinData? = null + set(value) { + if (field == null) { + field = value + } else { + throw IllegalStateException("The layout data for this element has already been set.") + } + } /** * Whether the element is in edit mode or not. @@ -35,38 +48,75 @@ abstract class HUDElement( } /** - * Returns the tag with a readable manner. + * Whether the element is selected or not. + */ + var isSelected = false + set(value) { + if (field != value) { + val background = background as HUDElementUnderlay + + if (value) { + background.select() + } else { + background.unselect() + } + field = value + } + } + + /** + * The line that connects this element to the parent's anchor + */ + var nodeLine: Line? = null + + /** + * Returns the name of this element. */ - val readableTag: String - get() = tag.replace("([a-z])([A-Z])".toRegex(), "$1 $2").lowercase().capitalize() + val name: String + get() = this::class.simpleName!!.substring(3).replace("([a-z])([A-Z])".toRegex(), "$1 $2") - override fun getParent(): GameplayHUD? { - return super.getParent() as? GameplayHUD - } + private var initialX = 0f + private var initialY = 0f open fun onEditModeChange(value: Boolean) { background = if (value) HUDElementUnderlay(this) else null } - fun onElementDataChange(data: HUDElementData) { - parent!!.removeConstraint(this) + fun onApplyElementSkinData() { - if (data.constraintTo != null) { - parent!!.addConstraint(this, parent!!.getElementByTag(data.constraintTo)!!) - } + val layout = elementData?.layout ?: return + + anchor = layout.anchor + origin = layout.origin - anchor = data.anchor - origin = data.origin - setScale(data.scale) - setPosition(data.constraintOffset.x, data.constraintOffset.y) + setScale(layout.scale) + setPosition(layout.position.x, layout.position.y) } - private var initialX = 0f - private var initialY = 0f + private fun calculateNearestAnchor(deltaX: Float, deltaY: Float) { + + val anchors = floatArrayOf(0f, 0.5f, 1f) + + val originX = drawX + drawWidth * origin.x + deltaX + val originY = drawY + drawHeight * origin.y + deltaY + + val nearestX = anchors.minBy { abs(originX - parent!!.drawWidth * it) } + val nearestY = anchors.minBy { abs(originY - parent!!.drawHeight * it) } + + if (nearestX != anchor.x) { + x = -x + } + + if (nearestY != anchor.y) { + y = -y + } + + anchor = Vec2(nearestX, nearestY) + } override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { @@ -75,7 +125,7 @@ abstract class HUDElement( val parentLocalY = drawY + localY if (event.action == TouchEvent.ACTION_DOWN) { - //parent!!.onElementSelected(this) + parent!!.selected = this initialX = parentLocalX initialY = parentLocalY return true @@ -85,8 +135,19 @@ abstract class HUDElement( val deltaX = parentLocalX - initialX val deltaY = parentLocalY - initialY + calculateNearestAnchor(deltaX, deltaY) setPosition(x + deltaX, y + deltaY) + nodeLine?.toPoint = Vec2( + parent!!.drawWidth * anchor.x, + parent!!.drawHeight * anchor.y + ) + + nodeLine?.fromPoint = Vec2( + drawX + drawWidth * origin.x, + drawY + drawHeight * origin.y + ) + initialX = parentLocalX initialY = parentLocalY return true @@ -97,6 +158,7 @@ abstract class HUDElement( } + //region Gameplay Events open fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { // Override this method to update the element with the latest gameplay data. } @@ -108,25 +170,32 @@ abstract class HUDElement( open fun onBreakStateChange(isBreak: Boolean) { // Override this method to handle break state changes. } + //endregion + + + override fun getParent(): GameplayHUD? { + return super.getParent() as? GameplayHUD + } } class HUDElementUnderlay(private val element: HUDElement) : Container() { - private val tagText = ExtendedText() + private val nameText = ExtendedText() init { - tagText.font = ResourceManager.getInstance().getFont("smallFont") - tagText.color = ColorARGB.White - tagText.text = element.readableTag - attachChild(tagText) + nameText.font = ResourceManager.getInstance().getFont("smallFont") + nameText.color = ColorARGB(0xFFF27272) + nameText.text = element.name + nameText.isVisible = false + attachChild(nameText) autoSizeAxes = Axes.None background = RoundedBox().apply { - color = ColorARGB.Red + color = ColorARGB(0x29F27272) alpha = 0.25f } } @@ -134,20 +203,31 @@ class HUDElementUnderlay(private val element: HUDElement) : Container() { override fun onManagedUpdate(pSecondsElapsed: Float) { if (element.drawY - drawHeight <= 0f) { - tagText.anchor = Anchor.BottomLeft - tagText.origin = Anchor.TopLeft + nameText.anchor = Anchor.BottomLeft + nameText.origin = Anchor.TopLeft } else { - tagText.anchor = Anchor.TopLeft - tagText.origin = Anchor.BottomLeft + nameText.anchor = Anchor.TopLeft + nameText.origin = Anchor.BottomLeft } // Cancel the scaling of the HUD element so the text is not affected by it, the same goes for the background. - tagText.setScale(1f / element.scaleX, 1f / element.scaleY) + nameText.setScale(1f / element.scaleX, 1f / element.scaleY) (background as RoundedBox).cornerRadius = 6f * (1f / element.scaleX) } + fun select() { + background!!.color = ColorARGB(0x80F27272) + nameText.isVisible = true + element.nodeLine?.isVisible = true + } + + fun unselect() { + background!!.color = ColorARGB(0x29F27272) + nameText.isVisible = false + element.nodeLine?.isVisible = false + } } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt index 54ec57637..bc3609e09 100644 --- a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt +++ b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt @@ -13,7 +13,7 @@ import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* import ru.nsu.ccfit.zuev.skins.* -class HUDHealthBar : HUDElement(tag = "healthBar") { +class HUDHealthBar : HUDElement() { private val fill: AnimatedSprite private val fillClear: Box @@ -87,6 +87,8 @@ class HUDHealthBar : HUDElement(tag = "healthBar") { fillClear.width = 0f fillClear.height = fill.drawHeight fillClear.setPosition(fill.x + fill.drawWidth, fill.y) + + onMeasureContentSize() } diff --git a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt index 99f4d680b..04c8ea06c 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt @@ -8,13 +8,14 @@ import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 import ru.nsu.ccfit.zuev.skins.OsuSkin import kotlin.math.roundToInt -class HUDPPCounter : HUDElement(tag = "ppCounter") { +class HUDPPCounter : HUDElement() { private val sprite = SpriteFont(OsuSkin.get().scorePrefix) init { setValue(0.0) attachChild(sprite) + onMeasureContentSize() } fun setValue(value: Double) { diff --git a/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt index 2a21f4076..9495af413 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt @@ -1,13 +1,16 @@ package com.reco1l.osu.hud.elements +import androidx.annotation.IntDef import com.reco1l.andengine.* import com.reco1l.andengine.shape.* import com.reco1l.framework.* +import com.reco1l.osu.hud.elements.ProgressIndicatorType.Companion.BAR +import com.reco1l.osu.hud.elements.ProgressIndicatorType.Companion.PIE import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 -class HUDPieSongProgress : HUDElement(tag = "pieSongProgress") { +class HUDPieSongProgress : HUDElement() { override var autoSizeAxes = Axes.Both @@ -63,9 +66,6 @@ class HUDPieSongProgress : HUDElement(tag = "pieSongProgress") { } onMeasureContentSize() - - anchor = Anchor.TopRight - origin = Anchor.CenterRight } @@ -89,4 +89,12 @@ class HUDPieSongProgress : HUDElement(tag = "pieSongProgress") { } } +} + +@IntDef(PIE, BAR) +annotation class ProgressIndicatorType { + companion object { + const val PIE = 0 + const val BAR = 1 + } } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt b/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt index 5c0ab03b2..a80ff92bd 100644 --- a/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt @@ -8,7 +8,7 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale -class HUDScoreCounter : HUDElement(tag = "scoreCounter") { +class HUDScoreCounter : HUDElement() { private val sprite = SpriteFont(OsuSkin.get().scorePrefix) private val format = DecimalFormat("00000000", DecimalFormatSymbols(Locale.US)) @@ -17,6 +17,8 @@ class HUDScoreCounter : HUDElement(tag = "scoreCounter") { sprite.spacing = -OsuSkin.get().scoreOverlap sprite.text = format.format(0) attachChild(sprite) + + onMeasureContentSize() } override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { diff --git a/src/ru/nsu/ccfit/zuev/osu/Config.java b/src/ru/nsu/ccfit/zuev/osu/Config.java index 765a00d71..7683a753e 100644 --- a/src/ru/nsu/ccfit/zuev/osu/Config.java +++ b/src/ru/nsu/ccfit/zuev/osu/Config.java @@ -18,7 +18,7 @@ import java.util.UUID; import com.reco1l.osu.multiplayer.Multiplayer; -import com.reco1l.osu.hud.ProgressIndicatorType; +import com.reco1l.osu.hud.elements.ProgressIndicatorType; import net.margaritov.preference.colorpicker.ColorPickerPreference; diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index ad90c29b7..904be50c8 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -32,7 +32,7 @@ import com.reco1l.andengine.ExtendedScene; import com.reco1l.osu.hitobjects.FollowPointConnection; import com.reco1l.osu.hud.GameplayHUD; -import com.reco1l.osu.hud.ProgressIndicatorType; +import com.reco1l.osu.hud.elements.ProgressIndicatorType; import com.reco1l.osu.hitobjects.SliderTickSprite; import com.reco1l.osu.ui.BlockAreaFragment; import com.reco1l.osu.ui.entity.GameplayLeaderboard; From 2563107b3ed556db49d05abeb45e24d48654d26f Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 20:52:35 -0300 Subject: [PATCH 09/85] Implement size modifiers --- .../andengine/modifier/IModifierChain.kt | 29 +++++++++++++++ .../reco1l/andengine/modifier/ModifierType.kt | 36 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/com/reco1l/andengine/modifier/IModifierChain.kt b/src/com/reco1l/andengine/modifier/IModifierChain.kt index 03bcd722a..42f3dc078 100644 --- a/src/com/reco1l/andengine/modifier/IModifierChain.kt +++ b/src/com/reco1l/andengine/modifier/IModifierChain.kt @@ -192,4 +192,33 @@ interface IModifierChain { } } + + // Size + fun sizeTo(width: Float, height: Float, durationSec: Float = 0f, easing: Easing = Easing.None): UniversalModifier { + return applyModifier { + type = SizeXY + duration = durationSec + finalValues = floatArrayOf(width, height) + eased(easing) + } + } + + fun sizeToX(width: Float, durationSec: Float = 0f, easing: Easing = Easing.None): UniversalModifier { + return applyModifier { + type = SizeX + duration = durationSec + finalValues = floatArrayOf(width) + eased(easing) + } + } + + fun sizeToY(height: Float, durationSec: Float = 0f, easing: Easing = Easing.None): UniversalModifier { + return applyModifier { + type = SizeY + duration = durationSec + finalValues = floatArrayOf(height) + eased(easing) + } + } + } \ No newline at end of file diff --git a/src/com/reco1l/andengine/modifier/ModifierType.kt b/src/com/reco1l/andengine/modifier/ModifierType.kt index cce7fbb05..b78c639a8 100644 --- a/src/com/reco1l/andengine/modifier/ModifierType.kt +++ b/src/com/reco1l/andengine/modifier/ModifierType.kt @@ -85,6 +85,12 @@ enum class ModifierType { */ Parallel, + SizeX, + + SizeY, + + SizeXY, + /** * Does nothing, used as a delay modifier. */ @@ -111,6 +117,21 @@ enum class ModifierType { MoveY -> floatArrayOf(entity.y) MoveXY -> floatArrayOf(entity.x, entity.y) + SizeX -> { + entity as? ExtendedEntity ?: throw IllegalArgumentException("SizeX is only available for ExtendedEntity instances.") + floatArrayOf(entity.width) + } + + SizeY -> { + entity as? ExtendedEntity ?: throw IllegalArgumentException("SizeY is only available for ExtendedEntity instances.") + floatArrayOf(entity.height) + } + + SizeXY -> { + entity as? ExtendedEntity ?: throw IllegalArgumentException("SizeXY is only available for ExtendedEntity instances.") + floatArrayOf(entity.width, entity.height) + } + TranslateX -> { entity as? ExtendedEntity ?: throw IllegalArgumentException("TranslateX is only available for ExtendedEntity instances.") floatArrayOf(entity.translationX) @@ -174,6 +195,21 @@ enum class ModifierType { entity.translationY = valueAt(1) } + SizeX -> { + entity as? ExtendedEntity ?: throw IllegalArgumentException("SizeX is only available for ExtendedEntity instances.") + entity.width = valueAt(0) + } + + SizeY -> { + entity as? ExtendedEntity ?: throw IllegalArgumentException("SizeY is only available for ExtendedEntity instances.") + entity.height = valueAt(0) + } + + SizeXY -> { + entity as? ExtendedEntity ?: throw IllegalArgumentException("SizeXY is only available for ExtendedEntity instances.") + entity.setSize(valueAt(0), valueAt(1)) + } + Rotation -> entity.rotation = valueAt(0) else -> Unit From 1ed0b58c7dbcc0b61b697eaf744ab0af7bcca6c5 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 20:53:01 -0300 Subject: [PATCH 10/85] Do not hide the entire health bar --- src/com/reco1l/osu/hud/elements/HUDHealthBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt index bc3609e09..e910caec7 100644 --- a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt +++ b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt @@ -131,7 +131,7 @@ class HUDHealthBar : HUDElement() { } override fun onBreakStateChange(isBreak: Boolean) { - isVisible = !isBreak + isChildrenVisible = !isBreak } From b3b678d3a63483cc942732757763209ce6f58a8b Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Fri, 14 Feb 2025 20:53:20 -0300 Subject: [PATCH 11/85] Introduce Element selector and properties --- .../container/ScrollableContainer.kt | 2 + src/com/reco1l/osu/hud/GameplayHUD.kt | 20 +- .../reco1l/osu/hud/HUDElementProperties.kt | 114 ++++++++++ src/com/reco1l/osu/hud/HUDElementSelector.kt | 199 ++++++++++++++++++ 4 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 src/com/reco1l/osu/hud/HUDElementProperties.kt create mode 100644 src/com/reco1l/osu/hud/HUDElementSelector.kt diff --git a/src/com/reco1l/andengine/container/ScrollableContainer.kt b/src/com/reco1l/andengine/container/ScrollableContainer.kt index c6cf72417..65f418843 100644 --- a/src/com/reco1l/andengine/container/ScrollableContainer.kt +++ b/src/com/reco1l/andengine/container/ScrollableContainer.kt @@ -12,6 +12,8 @@ import kotlin.math.* open class ScrollableContainer : Container() { + override var autoSizeAxes = Axes.None + /** * Which axes the container can scroll on. */ diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index c80e82d52..8c4dac135 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -2,6 +2,7 @@ package com.reco1l.osu.hud import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes +import com.reco1l.andengine.attachTo import com.reco1l.andengine.container.Container import com.reco1l.andengine.shape.Line import com.reco1l.framework.ColorARGB @@ -17,6 +18,7 @@ import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.hud.* import org.anddev.andengine.entity.IEntity import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.GlobalManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* @@ -57,6 +59,11 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : } + private var elementSelector: HUDElementSelector? = null + + private var elementProperties: HUDElementProperties? = null + + init { // The engine we expect the HUD to be a effectively an instance of the AndEngine's HUD class. // Since we need Container features we set a HUD instance as the parent and we just need to @@ -66,11 +73,17 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : parent.registerTouchArea(this) parent.camera = GlobalManager.getInstance().engine.camera - relativeSizeAxes = Axes.Both - setSize(1f, 1f) + autoSizeAxes = Axes.None + setSize(Config.getRES_WIDTH().toFloat(), Config.getRES_HEIGHT().toFloat()) onSkinDataChange(skinData) onEditModeChange(isInEditMode) + + elementSelector = HUDElementSelector(this) attachTo parent + parent.registerTouchArea(elementSelector) + + elementProperties = HUDElementProperties(this) attachTo parent + parent.registerTouchArea(elementProperties) } @@ -173,6 +186,7 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : mChildren?.fastForEach { (it as? HUDElement)?.onGameplayUpdate(game, stat, pSecondsElapsed) } + elementSelector?.onGameplayUpdate(game, stat, pSecondsElapsed) super.onManagedUpdate(pSecondsElapsed) } @@ -190,12 +204,14 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : mChildren?.fastForEach { (it as? HUDElement)?.onNoteHit(statistics) } + elementSelector?.onNoteHit(statistics) } fun onBreakStateChange(isBreak: Boolean) { mChildren?.fastForEach { (it as? HUDElement)?.onBreakStateChange(isBreak) } + elementSelector?.onBreakStateChange(isBreak) } //endregion diff --git a/src/com/reco1l/osu/hud/HUDElementProperties.kt b/src/com/reco1l/osu/hud/HUDElementProperties.kt new file mode 100644 index 000000000..0580d588b --- /dev/null +++ b/src/com/reco1l/osu/hud/HUDElementProperties.kt @@ -0,0 +1,114 @@ +package com.reco1l.osu.hud + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.Axes +import com.reco1l.andengine.attachTo +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.container.LinearContainer +import com.reco1l.andengine.container.Orientation +import com.reco1l.andengine.container.ScrollableContainer +import com.reco1l.andengine.getPaddedHeight +import com.reco1l.andengine.getPaddedWidth +import com.reco1l.andengine.shape.Box +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.framework.ColorARGB +import com.reco1l.framework.math.Vec4 +import com.reco1l.osu.hud.data.HUDElementLayoutData +import com.reco1l.osu.hud.data.HUDElementSkinData +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.Config +import ru.nsu.ccfit.zuev.osu.ResourceManager + +class HUDElementProperties(private val hud: GameplayHUD) : Container() { + + + init { + + relativeSizeAxes = Axes.Y + height = 1f + x = Config.getRES_WIDTH().toFloat() - BUTTON_WIDTH + + // The button to show/hide the element selector + object : Container() { + + init { + background = RoundedBox().apply { + cornerRadius = BUTTON_RADIUS + color = ColorARGB(0xFF181825) + } + width = BUTTON_WIDTH + BUTTON_RADIUS + height = 150f + anchor = Anchor.CenterLeft + origin = Anchor.CenterLeft + + ExtendedText().apply { + rotation = 90f + anchor = Anchor.Center + origin = Anchor.Center + font = ResourceManager.getInstance().getFont("smallFont") + text = "Properties" + x = -(BUTTON_RADIUS / 2) + } attachTo this + } + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (event.isActionUp) { + + this@HUDElementProperties.clearEntityModifiers() + + if (this@HUDElementProperties.x < Config.getRES_WIDTH() - BUTTON_WIDTH) { + this@HUDElementProperties.moveToX(Config.getRES_WIDTH() - BUTTON_WIDTH, 0.2f) + + hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) + } else { + this@HUDElementProperties.moveToX(Config.getRES_WIDTH() - BUTTON_WIDTH - MENU_WIDTH, 0.2f) + + hud.sizeToX(Config.getRES_WIDTH() - MENU_WIDTH, 0.2f) + } + } + + return false + } + + } attachTo this + + ScrollableContainer().apply { + + scrollAxes = Axes.Y + relativeSizeAxes = Axes.Y + height = 1f + width = MENU_WIDTH + x = BUTTON_WIDTH + + indicatorY!!.width = 4f + + background = Box().apply { + color = ColorARGB(0xFF1E1E2E) + } + + val linearContainer = LinearContainer().apply { + relativeSizeAxes = Axes.X + width = 1f + padding = Vec4(16f) + spacing = 12f + orientation = Orientation.Vertical + } attachTo this + + + } attachTo this + + } + + + companion object { + + const val MENU_WIDTH = 300f + + const val BUTTON_WIDTH = 48f + + const val BUTTON_RADIUS = 12f + + } +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/HUDElementSelector.kt b/src/com/reco1l/osu/hud/HUDElementSelector.kt new file mode 100644 index 000000000..3dbacf4e9 --- /dev/null +++ b/src/com/reco1l/osu/hud/HUDElementSelector.kt @@ -0,0 +1,199 @@ +package com.reco1l.osu.hud + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.Axes +import com.reco1l.andengine.attachTo +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.container.LinearContainer +import com.reco1l.andengine.container.Orientation +import com.reco1l.andengine.container.ScrollableContainer +import com.reco1l.andengine.getPaddedHeight +import com.reco1l.andengine.getPaddedWidth +import com.reco1l.andengine.shape.Box +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.framework.ColorARGB +import com.reco1l.framework.math.Vec4 +import com.reco1l.osu.hud.data.HUDElementLayoutData +import com.reco1l.osu.hud.data.HUDElementSkinData +import com.reco1l.osu.hud.elements.HUDAccuracyCounter +import com.reco1l.osu.hud.elements.HUDComboCounter +import com.reco1l.osu.hud.elements.HUDElement +import com.reco1l.osu.hud.elements.HUDHealthBar +import com.reco1l.osu.hud.elements.HUDPPCounter +import com.reco1l.osu.hud.elements.HUDPieSongProgress +import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.toolkt.kotlin.fastForEach +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.Config +import ru.nsu.ccfit.zuev.osu.ResourceManager +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 + +class HUDElementSelector(private val hud: GameplayHUD) : Container() { + + + private val elements = createAllElements() + + + init { + + relativeSizeAxes = Axes.Y + height = 1f + x = -SELECTOR_WIDTH + + // The button to show/hide the element selector + object : Container() { + + init { + background = RoundedBox().apply { + cornerRadius = BUTTON_RADIUS + color = ColorARGB(0xFF181825) + } + width = BUTTON_WIDTH + BUTTON_RADIUS + height = 150f + x = SELECTOR_WIDTH - BUTTON_RADIUS + anchor = Anchor.CenterLeft + origin = Anchor.CenterLeft + + ExtendedText().apply { + rotation = -90f + anchor = Anchor.Center + origin = Anchor.Center + font = ResourceManager.getInstance().getFont("smallFont") + text = "Elements" + x = BUTTON_RADIUS / 2 + } attachTo this + } + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (event.isActionUp) { + + this@HUDElementSelector.clearEntityModifiers() + + if (this@HUDElementSelector.x < 0f) { + this@HUDElementSelector.moveToX(0f, 0.2f) + + hud.moveToX(SELECTOR_WIDTH, 0.2f) + hud.sizeToX(Config.getRES_WIDTH() - SELECTOR_WIDTH, 0.2f) + } else { + this@HUDElementSelector.moveToX(-SELECTOR_WIDTH, 0.2f) + + hud.moveToX(0f, 0.2f) + hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) + } + } + + return false + } + + } attachTo this + + ScrollableContainer().apply { + + scrollAxes = Axes.Y + relativeSizeAxes = Axes.Y + height = 1f + width = SELECTOR_WIDTH + + indicatorY!!.width = 4f + + background = Box().apply { + color = ColorARGB(0xFF1E1E2E) + } + + val linearContainer = LinearContainer().apply { + relativeSizeAxes = Axes.X + width = 1f + padding = Vec4(16f) + spacing = 12f + orientation = Orientation.Vertical + } attachTo this + + elements.forEach { element -> + + val elementWrapper = Container().apply { + relativeSizeAxes = Axes.X + width = 1f + height = 120f + padding = Vec4(12f) + background = RoundedBox().apply { + color = ColorARGB(0xFF363653) + cornerRadius = 12f + } + } attachTo linearContainer + + ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + anchor = Anchor.BottomLeft + origin = Anchor.BottomLeft + text = element.name + color = ColorARGB.White + } attachTo elementWrapper + + element.elementData = HUDElementSkinData(element::class, HUDElementLayoutData()) + element attachTo elementWrapper + element.onApplyElementSkinData() + + // Scaling the element inside the box + + if (element.drawHeight > elementWrapper.getPaddedHeight()) { + element.setScale(elementWrapper.getPaddedHeight() / element.drawHeight) + } + + if (element.drawWidth > elementWrapper.getPaddedWidth()) { + element.setScale(elementWrapper.getPaddedWidth() / element.drawWidth) + } + } + + } attachTo this + + } + + + //region Gameplay Events + fun onNoteHit(statistics: StatisticV2) { + elements.fastForEach { + (it as? HUDElement)?.onNoteHit(statistics) + } + } + + fun onBreakStateChange(isBreak: Boolean) { + elements.fastForEach { + (it as? HUDElement)?.onBreakStateChange(isBreak) + } + } + + fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + elements.fastForEach { + (it as? HUDElement)?.onGameplayUpdate(game, statistics, secondsElapsed) + } + } + //endregion + + + companion object { + + + const val SELECTOR_WIDTH = 300f + + const val BUTTON_WIDTH = 48f + + const val BUTTON_RADIUS = 12f + + + fun createAllElements(): List { + return listOf( + HUDAccuracyCounter(), + HUDComboCounter(), + HUDHealthBar(), + HUDPieSongProgress(), + HUDPPCounter(), + HUDScoreCounter() + ) + } + + } + +} \ No newline at end of file From d8b4b1e88d5b642bef7008781136d3ee94749344 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sat, 15 Feb 2025 01:50:35 -0300 Subject: [PATCH 12/85] Improve input system --- src/com/reco1l/andengine/ExtendedEntity.kt | 19 ++- .../container/ScrollableContainer.kt | 27 +++-- src/com/reco1l/osu/hud/GameplayHUD.kt | 101 +++++++++------- src/com/reco1l/osu/hud/HUDElementSelector.kt | 113 ++++++++++++------ .../osu/hud/elements/HUDComboCounter.kt | 25 +++- src/com/reco1l/osu/hud/elements/HUDElement.kt | 4 +- 6 files changed, 193 insertions(+), 96 deletions(-) diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index bb2017c87..bc5a793e3 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -5,6 +5,7 @@ import com.reco1l.andengine.container.* import com.reco1l.andengine.modifier.* import com.reco1l.framework.* import com.reco1l.framework.math.Vec4 +import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.* import org.anddev.andengine.entity.* import org.anddev.andengine.entity.scene.Scene @@ -366,9 +367,15 @@ abstract class ExtendedEntity( } } - open fun invalidateTransformations() { + open fun invalidateTransformations(recursively: Boolean = true) { mLocalToParentTransformationDirty = true mParentToLocalTransformationDirty = true + + if (recursively) { + mChildren?.fastForEach { + (it as? ExtendedEntity)?.invalidateTransformations() + } + } } @@ -829,6 +836,16 @@ abstract class ExtendedEntity( // Input + open fun invalidateInputBinding(recursively: Boolean = true) { + currentBoundEntity = null + + if (recursively) { + mChildren?.fastForEach { + (it as? ExtendedEntity)?.invalidateInputBinding() + } + } + } + override fun onAreaTouched( event: TouchEvent, localX: Float, diff --git a/src/com/reco1l/andengine/container/ScrollableContainer.kt b/src/com/reco1l/andengine/container/ScrollableContainer.kt index 65f418843..094d5a0a5 100644 --- a/src/com/reco1l/andengine/container/ScrollableContainer.kt +++ b/src/com/reco1l/andengine/container/ScrollableContainer.kt @@ -36,6 +36,7 @@ open class ScrollableContainer : Container() { indicatorX?.alpha = 0.5f field = value + invalidateTransformations() } /** @@ -55,6 +56,7 @@ open class ScrollableContainer : Container() { indicatorY?.alpha = 0.5f field = value + invalidateTransformations() } /** @@ -280,6 +282,11 @@ open class ScrollableContainer : Container() { override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (super.onAreaTouched(event, localX, localY)) { + return false + } + invalidateInputBinding() + when (event.action) { ACTION_DOWN -> { @@ -288,6 +295,7 @@ open class ScrollableContainer : Container() { velocityX = 0f velocityY = 0f + return true } ACTION_MOVE -> { @@ -295,12 +303,10 @@ open class ScrollableContainer : Container() { var deltaX = if (scrollAxes.isHorizontal) localX - initialX else 0f var deltaY = if (scrollAxes.isVertical) localY - initialY else 0f - if (!isDragging) { - isDragging = abs(deltaX) > 1f || abs(deltaY) > 1f + isDragging = abs(deltaX) > 1f || abs(deltaY) > 1f - if (!isDragging) { - return super.onAreaTouched(event, localX, localY) - } + if (!isDragging) { + return super.onAreaTouched(event, localX, localY) } val length = hypot(deltaX, deltaY) @@ -328,18 +334,17 @@ open class ScrollableContainer : Container() { } lastDragTimeSec = elapsedTimeSec + return true } else -> { + if (!isDragging) { + return super.onAreaTouched(event, localX, localY) + } isDragging = false + return false } } - - if (!isDragging) { - return super.onAreaTouched(event, localX, localY) - } - - return true } diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 8c4dac135..78631879c 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -7,6 +7,7 @@ import com.reco1l.andengine.container.Container import com.reco1l.andengine.shape.Line import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.hud.data.HUDElementSkinData import com.reco1l.osu.hud.data.HUDSkinData import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDComboCounter @@ -23,7 +24,10 @@ import ru.nsu.ccfit.zuev.osu.GlobalManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* -class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : Container() { +class GameplayHUD( + private val statistics: StatisticV2, + private val gameScene: GameScene +) : Container() { /** @@ -78,17 +82,43 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : onSkinDataChange(skinData) onEditModeChange(isInEditMode) + } + - elementSelector = HUDElementSelector(this) attachTo parent - parent.registerTouchArea(elementSelector) + fun addElement(data: HUDElementSkinData, inEditMode: Boolean = isInEditMode) { + val element = data.type.create() + element.elementData = data + attachChild(element) + addAnchorNodeLine(element) - elementProperties = HUDElementProperties(this) attachTo parent - parent.registerTouchArea(elementProperties) + element.isInEditMode = inEditMode } private fun onEditModeChange(value: Boolean) { - mChildren?.forEach { (it as? HUDElement)?.isInEditMode = value } + + mChildren?.forEach { + (it as? HUDElement)?.isInEditMode = value + } + + val parent = parent!! + + if (value) { + elementSelector = HUDElementSelector(this) attachTo parent + elementProperties = HUDElementProperties(this) attachTo parent + + parent.registerTouchArea(elementSelector) + parent.registerTouchArea(elementProperties) + } else { + parent.unregisterTouchArea(elementSelector) + parent.unregisterTouchArea(elementProperties) + + elementSelector?.detachSelf() + elementSelector = null + + elementProperties?.detachSelf() + elementProperties = null + } } private fun onSkinDataChange(layoutData: HUDSkinData) { @@ -97,18 +127,12 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : // First pass: We attach everything so that elements can reference between them when // applying default layout. - layoutData.elements.forEach { data -> - val element = data.type.create() - element.elementData = data - attachChild(element) - } + layoutData.elements.forEach { data -> addElement(data) } applyDefaultLayout() // Second pass: Apply custom element data that will override the default layout if any. - mChildren?.forEach { (it as? HUDElement)?.onApplyElementSkinData() } - - addAnchorNodes() + mChildren?.forEach { (it as? HUDElement)?.onSkinDataChange(it.elementData) } } private fun applyDefaultLayout() { @@ -148,45 +172,36 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : comboCounter?.setScale(1.28f) } - private fun addAnchorNodes() { - - fun addAnchorNodeLine(element: HUDElement) { + private fun addAnchorNodeLine(element: HUDElement) { - val pointOnParent = Vec2( - drawWidth * element.anchor.x, - drawHeight * element.anchor.y - ) + val pointOnParent = Vec2( + drawWidth * element.anchor.x, + drawHeight * element.anchor.y + ) - val pointFromChild = Vec2( - element.drawX + element.drawWidth * element.origin.x, - element.drawY + element.drawHeight * element.origin.y - ) - - element.nodeLine = Line().apply { - fromPoint = pointFromChild - toPoint = pointOnParent - color = ColorARGB(0xFFF27272) - lineWidth = 10f - isVisible = false - } + val pointFromChild = Vec2( + element.drawX + element.drawWidth * element.origin.x, + element.drawY + element.drawHeight * element.origin.y + ) - attachChild(element.nodeLine!!) + element.nodeLine = Line().apply { + fromPoint = pointFromChild + toPoint = pointOnParent + color = ColorARGB(0xFFF27272) + lineWidth = 10f + isVisible = false } - mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) - mChildren?.toList()?.forEach { - if (it is HUDElement) { - addAnchorNodeLine(it) - } - } + attachChild(element.nodeLine!!) } + //region Entity events override fun onManagedUpdate(pSecondsElapsed: Float) { mChildren?.fastForEach { - (it as? HUDElement)?.onGameplayUpdate(game, stat, pSecondsElapsed) + (it as? HUDElement)?.onGameplayUpdate(gameScene, statistics, pSecondsElapsed) } - elementSelector?.onGameplayUpdate(game, stat, pSecondsElapsed) + elementSelector?.onGameplayUpdate(gameScene, statistics, pSecondsElapsed) super.onManagedUpdate(pSecondsElapsed) } @@ -197,7 +212,7 @@ class GameplayHUD(private val stat: StatisticV2, private val game: GameScene) : selected = null return false } - + //endregion //region Gameplay Events fun onNoteHit(statistics: StatisticV2) { diff --git a/src/com/reco1l/osu/hud/HUDElementSelector.kt b/src/com/reco1l/osu/hud/HUDElementSelector.kt index 3dbacf4e9..8cb530cd2 100644 --- a/src/com/reco1l/osu/hud/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/HUDElementSelector.kt @@ -14,7 +14,6 @@ import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec4 -import com.reco1l.osu.hud.data.HUDElementLayoutData import com.reco1l.osu.hud.data.HUDElementSkinData import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDComboCounter @@ -29,6 +28,7 @@ import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import kotlin.math.abs class HUDElementSelector(private val hud: GameplayHUD) : Container() { @@ -112,39 +112,7 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container() { } attachTo this elements.forEach { element -> - - val elementWrapper = Container().apply { - relativeSizeAxes = Axes.X - width = 1f - height = 120f - padding = Vec4(12f) - background = RoundedBox().apply { - color = ColorARGB(0xFF363653) - cornerRadius = 12f - } - } attachTo linearContainer - - ExtendedText().apply { - font = ResourceManager.getInstance().getFont("smallFont") - anchor = Anchor.BottomLeft - origin = Anchor.BottomLeft - text = element.name - color = ColorARGB.White - } attachTo elementWrapper - - element.elementData = HUDElementSkinData(element::class, HUDElementLayoutData()) - element attachTo elementWrapper - element.onApplyElementSkinData() - - // Scaling the element inside the box - - if (element.drawHeight > elementWrapper.getPaddedHeight()) { - element.setScale(elementWrapper.getPaddedHeight() / element.drawHeight) - } - - if (element.drawWidth > elementWrapper.getPaddedWidth()) { - element.setScale(elementWrapper.getPaddedWidth() / element.drawWidth) - } + HUDElementPreview(element, hud) attachTo linearContainer } } attachTo this @@ -196,4 +164,81 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container() { } +} + +class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Container() { + + + init { + width = HUDElementSelector.SELECTOR_WIDTH - 16f * 2 + height = 120f + padding = Vec4(12f) + background = RoundedBox().apply { + color = ColorARGB(0xFF363653) + cornerRadius = 12f + } + scaleCenterX = 0.5f + scaleCenterY = 0.5f + + ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + anchor = Anchor.BottomLeft + origin = Anchor.BottomLeft + text = element.name + color = ColorARGB.White + } attachTo this + + element attachTo this + element.onSkinDataChange(HUDElementSkinData(element::class)) + + // Scaling the element inside the box + + if (element.drawHeight > getPaddedHeight()) { + element.setScale(getPaddedHeight() / element.drawHeight) + } + + if (element.drawWidth > getPaddedWidth()) { + element.setScale(getPaddedWidth() / element.drawWidth) + } + } + + //region Input handling + private var initialX = 0f + private var initialY = 0f + private var initialTime = 0L + private var wasMoved = false + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (event.isActionDown) { + initialX = localX + initialY = localY + initialTime = System.currentTimeMillis() + + clearEntityModifiers() + scaleTo(0.9f, 0.1f) + return true + } + + if (event.isActionMove) { + clearEntityModifiers() + scaleTo(1f, 0.1f) + } + + if (event.isActionUp) { + clearEntityModifiers() + scaleTo(1f, 0.1f) + + wasMoved = abs(localX - initialX) > 1f && abs(localY - initialY) > 1f + + if (!wasMoved && System.currentTimeMillis() - initialTime > 50) { + hud.addElement(HUDElementSkinData(element::class)) + return false + } + } + + return false + } + //endregion + } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt index 9e67d608c..41ca682a2 100644 --- a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt @@ -1,8 +1,9 @@ package com.reco1l.osu.hud.elements import com.edlplan.framework.easing.* -import com.reco1l.andengine.* import com.reco1l.andengine.modifier.OnModifierFinished +import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.hud.data.HUDElementSkinData import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.* import ru.nsu.ccfit.zuev.osu.game.GameScene @@ -12,13 +13,19 @@ import ru.nsu.ccfit.zuev.skins.* class HUDComboCounter : HUDElement() { + override var origin: Vec2 + get() = super.origin + set(value) { + super.origin = value + popOutCount?.origin = value + displayedCountTextSprite.origin = value + } + private val popOutCount = if (Config.isAnimateComboText()) SpriteFont(OsuSkin.get().comboPrefix).also { it.alpha = 0f it.text = "0x" - it.anchor = Anchor.BottomLeft - it.origin = Anchor.BottomLeft it.spacing = -OsuSkin.get().comboOverlap // In stable, the bigger pop out scales a bit to the left @@ -31,8 +38,6 @@ class HUDComboCounter : HUDElement() { private val displayedCountTextSprite = SpriteFont(OsuSkin.get().comboPrefix).also { it.text = "0x" - it.anchor = Anchor.BottomLeft - it.origin = Anchor.BottomLeft it.spacing = -OsuSkin.get().comboOverlap attachChild(it, 0) @@ -87,10 +92,20 @@ class HUDComboCounter : HUDElement() { } + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { setCombo(statistics.combo) } + override fun onSkinDataChange(data: HUDElementSkinData?) { + super.onSkinDataChange(data) + + displayedCountTextSprite.anchor = anchor + displayedCountTextSprite.origin = origin + popOutCount?.origin = origin + popOutCount?.anchor = anchor + } + companion object { const val BIG_POP_OUT_DURATION = 0.3f diff --git a/src/com/reco1l/osu/hud/elements/HUDElement.kt b/src/com/reco1l/osu/hud/elements/HUDElement.kt index add8dc01b..ad413504f 100644 --- a/src/com/reco1l/osu/hud/elements/HUDElement.kt +++ b/src/com/reco1l/osu/hud/elements/HUDElement.kt @@ -85,9 +85,9 @@ abstract class HUDElement : Container() { } - fun onApplyElementSkinData() { + open fun onSkinDataChange(data: HUDElementSkinData?) { - val layout = elementData?.layout ?: return + val layout = data?.layout ?: return anchor = layout.anchor origin = layout.origin From c24ecf098d5cc774233ebf9e0d09af3990ee20ed Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sat, 15 Feb 2025 19:05:01 -0300 Subject: [PATCH 13/85] Remove temporarily element properties --- src/com/reco1l/andengine/ExtendedEntity.kt | 36 +++--- .../reco1l/osu/hud/HUDElementProperties.kt | 114 ------------------ src/com/reco1l/osu/hud/HUDElementSelector.kt | 6 +- 3 files changed, 23 insertions(+), 133 deletions(-) delete mode 100644 src/com/reco1l/osu/hud/HUDElementProperties.kt diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index bc5a793e3..22a4d40f9 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -367,14 +367,12 @@ abstract class ExtendedEntity( } } - open fun invalidateTransformations(recursively: Boolean = true) { + open fun invalidateTransformations() { mLocalToParentTransformationDirty = true mParentToLocalTransformationDirty = true - if (recursively) { - mChildren?.fastForEach { - (it as? ExtendedEntity)?.invalidateTransformations() - } + mChildren?.fastForEach { + (it as? ExtendedEntity)?.invalidateTransformations() } } @@ -473,14 +471,6 @@ abstract class ExtendedEntity( applyBlending(pGL) } - override fun doDraw(gl: GL10, camera: Camera) { - - background?.setSize(drawWidth, drawHeight) - background?.onDraw(gl, camera) - - super.doDraw(gl, camera) - } - override fun onDrawChildren(gl: GL10, camera: Camera) { val hasPaddingApplicable = padding.left > 0f || padding.top > 0f @@ -526,9 +516,6 @@ abstract class ExtendedEntity( if (hasPaddingApplicable) { gl.glTranslatef(-padding.right, -padding.top, 0f) } - - foreground?.setSize(drawWidth, drawHeight) - foreground?.onDraw(gl, camera) } override fun onManagedDraw(gl: GL10, camera: Camera) { @@ -538,7 +525,22 @@ abstract class ExtendedEntity( onUpdateVertexBuffer() } - super.onManagedDraw(gl, camera) + gl.glPushMatrix() + + if (!isCullingEnabled || !isCulled(camera)) { + onApplyTransformations(gl, camera) + + background?.setSize(drawWidth, drawHeight) + background?.onDraw(gl, camera) + + doDraw(gl, camera) + onDrawChildren(gl, camera) + + foreground?.setSize(drawWidth, drawHeight) + foreground?.onDraw(gl, camera) + } + + gl.glPopMatrix() } override fun onInitDraw(pGL: GL10) { diff --git a/src/com/reco1l/osu/hud/HUDElementProperties.kt b/src/com/reco1l/osu/hud/HUDElementProperties.kt deleted file mode 100644 index 0580d588b..000000000 --- a/src/com/reco1l/osu/hud/HUDElementProperties.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.reco1l.osu.hud - -import com.reco1l.andengine.Anchor -import com.reco1l.andengine.Axes -import com.reco1l.andengine.attachTo -import com.reco1l.andengine.container.Container -import com.reco1l.andengine.container.LinearContainer -import com.reco1l.andengine.container.Orientation -import com.reco1l.andengine.container.ScrollableContainer -import com.reco1l.andengine.getPaddedHeight -import com.reco1l.andengine.getPaddedWidth -import com.reco1l.andengine.shape.Box -import com.reco1l.andengine.shape.RoundedBox -import com.reco1l.andengine.text.ExtendedText -import com.reco1l.framework.ColorARGB -import com.reco1l.framework.math.Vec4 -import com.reco1l.osu.hud.data.HUDElementLayoutData -import com.reco1l.osu.hud.data.HUDElementSkinData -import org.anddev.andengine.input.touch.TouchEvent -import ru.nsu.ccfit.zuev.osu.Config -import ru.nsu.ccfit.zuev.osu.ResourceManager - -class HUDElementProperties(private val hud: GameplayHUD) : Container() { - - - init { - - relativeSizeAxes = Axes.Y - height = 1f - x = Config.getRES_WIDTH().toFloat() - BUTTON_WIDTH - - // The button to show/hide the element selector - object : Container() { - - init { - background = RoundedBox().apply { - cornerRadius = BUTTON_RADIUS - color = ColorARGB(0xFF181825) - } - width = BUTTON_WIDTH + BUTTON_RADIUS - height = 150f - anchor = Anchor.CenterLeft - origin = Anchor.CenterLeft - - ExtendedText().apply { - rotation = 90f - anchor = Anchor.Center - origin = Anchor.Center - font = ResourceManager.getInstance().getFont("smallFont") - text = "Properties" - x = -(BUTTON_RADIUS / 2) - } attachTo this - } - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - - if (event.isActionUp) { - - this@HUDElementProperties.clearEntityModifiers() - - if (this@HUDElementProperties.x < Config.getRES_WIDTH() - BUTTON_WIDTH) { - this@HUDElementProperties.moveToX(Config.getRES_WIDTH() - BUTTON_WIDTH, 0.2f) - - hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) - } else { - this@HUDElementProperties.moveToX(Config.getRES_WIDTH() - BUTTON_WIDTH - MENU_WIDTH, 0.2f) - - hud.sizeToX(Config.getRES_WIDTH() - MENU_WIDTH, 0.2f) - } - } - - return false - } - - } attachTo this - - ScrollableContainer().apply { - - scrollAxes = Axes.Y - relativeSizeAxes = Axes.Y - height = 1f - width = MENU_WIDTH - x = BUTTON_WIDTH - - indicatorY!!.width = 4f - - background = Box().apply { - color = ColorARGB(0xFF1E1E2E) - } - - val linearContainer = LinearContainer().apply { - relativeSizeAxes = Axes.X - width = 1f - padding = Vec4(16f) - spacing = 12f - orientation = Orientation.Vertical - } attachTo this - - - } attachTo this - - } - - - companion object { - - const val MENU_WIDTH = 300f - - const val BUTTON_WIDTH = 48f - - const val BUTTON_RADIUS = 12f - - } -} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/HUDElementSelector.kt b/src/com/reco1l/osu/hud/HUDElementSelector.kt index 8cb530cd2..8600ee3c0 100644 --- a/src/com/reco1l/osu/hud/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/HUDElementSelector.kt @@ -10,6 +10,7 @@ import com.reco1l.andengine.container.ScrollableContainer import com.reco1l.andengine.getPaddedHeight import com.reco1l.andengine.getPaddedWidth import com.reco1l.andengine.shape.Box +import com.reco1l.andengine.shape.Circle import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB @@ -173,12 +174,13 @@ class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Containe width = HUDElementSelector.SELECTOR_WIDTH - 16f * 2 height = 120f padding = Vec4(12f) + scaleCenterX = 0.5f + scaleCenterY = 0.5f + background = RoundedBox().apply { color = ColorARGB(0xFF363653) cornerRadius = 12f } - scaleCenterX = 0.5f - scaleCenterY = 0.5f ExtendedText().apply { font = ResourceManager.getInstance().getFont("smallFont") From 2c6ca421964ce6e352afeebf18d2658168e6be9e Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sun, 16 Feb 2025 01:08:46 -0300 Subject: [PATCH 14/85] Tidy code --- assets/delete.png | Bin 0 -> 966 bytes assets/expand.png | Bin 0 -> 687 bytes assets/rotate_left.png | Bin 0 -> 1455 bytes assets/rotate_right.png | Bin 0 -> 1484 bytes res/xml/settings_gameplay.xml | 6 + src/com/reco1l/andengine/Anchor.kt | 31 ++ src/com/reco1l/framework/math/Vec2.kt | 7 + src/com/reco1l/osu/hud/GameplayHUD.kt | 170 ++++++----- src/com/reco1l/osu/hud/HUDElement.kt | 265 ++++++++++++++++++ src/com/reco1l/osu/hud/HUDSkinData.kt | 106 +++++++ src/com/reco1l/osu/hud/data/HUDSkinData.kt | 74 ----- .../osu/hud/editor/HUDElementPreview.kt | 94 +++++++ .../hud/{ => editor}/HUDElementSelector.kt | 145 ++-------- .../osu/hud/editor/HUDElementToolbar.kt | 89 ++++++ .../osu/hud/elements/HUDAccuracyCounter.kt | 1 + .../osu/hud/elements/HUDComboCounter.kt | 27 +- src/com/reco1l/osu/hud/elements/HUDElement.kt | 233 --------------- .../reco1l/osu/hud/elements/HUDHealthBar.kt | 1 + .../reco1l/osu/hud/elements/HUDPPCounter.kt | 1 + .../osu/hud/elements/HUDPieSongProgress.kt | 1 + .../osu/hud/elements/HUDScoreCounter.kt | 1 + src/com/reco1l/osu/ui/SettingsFragment.kt | 24 ++ src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 37 ++- src/ru/nsu/ccfit/zuev/skins/OsuSkin.java | 17 ++ .../nsu/ccfit/zuev/skins/SkinJsonReader.java | 11 + 25 files changed, 797 insertions(+), 544 deletions(-) create mode 100644 assets/delete.png create mode 100644 assets/expand.png create mode 100644 assets/rotate_left.png create mode 100644 assets/rotate_right.png create mode 100644 src/com/reco1l/osu/hud/HUDElement.kt create mode 100644 src/com/reco1l/osu/hud/HUDSkinData.kt delete mode 100644 src/com/reco1l/osu/hud/data/HUDSkinData.kt create mode 100644 src/com/reco1l/osu/hud/editor/HUDElementPreview.kt rename src/com/reco1l/osu/hud/{ => editor}/HUDElementSelector.kt (50%) create mode 100644 src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt delete mode 100644 src/com/reco1l/osu/hud/elements/HUDElement.kt diff --git a/assets/delete.png b/assets/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..feee5b14d4f353a95a831bda9ab59a1d4d87aea3 GIT binary patch literal 966 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>U~ceqaSW-5 zdpqZE?QI2tw!}$hv$ss%GIPmE6<@VXW6#YeL!UpGFY7AC!6#cL_cz;a$@hnGM@|Yb zPhZV+acj91KSO~W3j;&LW~Os@g&7$N!Vc`LR$^c{$7NCO&%$sZlJWVwP6h@G?FV;k z1sNJrSzVr_b3E*xXs55(a$Vx_>cHx>vpnLAN}o!F{*`k)oP9HRpXnF#mI8aJ7D2`p zKdgRL$M72-J1)n2eVe5c!^+wjld6Op7S6l*C2dmv{}&1Js<+++y5+u!e0%29sWS{6 zC)Rs5w%dkCEa0E(Jx}M|VW9*21)BU8%zt=^<44}d1q>^K1LHH_zgo3#eJbYzhAcsffhk+pDjSjmi*wSVGGjaduiVlFS)^TW#WR`!ECZr}FhGo0aIIFOWSlISL0`YX|Ge~RrfiBiQWdtF>! zXM~0A*Uo4P5chYDKg>|Tv|-Af-KRNc9>~1=d9_6)Tf_D#ZOe?FDXm&?)8vC!lnuB3 z(%`b#gZw*%{47=fnpp0im(=q;TTVr3x?N`4L&gUw_OI?ToH$pe%^J053P(oZKa;N2 zdqo%+ek^cUZ(=q1gU>6m*J~uVGcX)DUU*n0K-jxYnG=`*gwhnRNwDhX3r7Sj{;}3l z6c}fg4VDWGxn?k*Vo6cW*-@uC`A@ATP&mZJ_esiY1NIbEE0F_D8x(X_=!BU~JJfWF z%B7DzVw@^!mj&wQU-*o9dfoCC%iwuAR8sl&ft{%XYh#)JlekRgo9P7_?kA zuVG^FHr2kx9aI5YOp>f*Z$F(;m1xwkxhZ{D}! z6FgsPHkG_+OEX>nQ|972eco8cB^np5Y&yq&o$cX_BiddJ*Z7{iW9Tz4E8DWSNkX!1 zUe~{*N~?ELbM7)+>6;L}o}D565V5&|A)R&3?*IIif@yuvir78@GbV$ltDnm{r-UW| D^CX|) literal 0 HcmV?d00001 diff --git a/assets/expand.png b/assets/expand.png new file mode 100644 index 0000000000000000000000000000000000000000..c6827c4f732f97c2d4fad29f5d32736c67619887 GIT binary patch literal 687 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>V9NA#aSW-5 zdpqZ%?_mc4)_m(mKM&OwFM+8O{5&*YJ~^>aLGj6gt?%c}&r#dI=DVeFT>BHhzC)+I z6xO_K5oej9&%~l|z~_L>dGe_Qo5rZu*_uxyyeeP3sM{&~TS)MEpIyD&uHBp~wwir- z-`pr0BJ@q;!Qv^OOd<|gD)BW}*KKun=zFEABv-uj^Qvze4u#xWf{KZ%nO5(mB%))p zbLIEE75ifIDXgVoJF+QuMyGbNry#c}1SB}{d1g7Tj0|8-i-wB(((@P=>URi+xD^-CL8%5P1X9TVDc zrFJS)*xg6V*_OWQN9Yq+pfvkUcY}&Xs3cQJ%H&$X2`o#soEy4wHt91>S`d=T5irsB zo2!G+lvO+k$E?*$cqpCeuXpol)uAw!n(E29f-x0|CGy)$8Fxqf2XNiElAOFt|7k1l z6sB9u#gPEYXWT23+P*yetDQq9|3k|ME6)l~xZCt;`G@`5f(hUH8CYg;&UieN dY-)3Ze_F@kM2q^BtiTk=;OXk;vd$@?2>>G39z*~D literal 0 HcmV?d00001 diff --git a/assets/rotate_left.png b/assets/rotate_left.png new file mode 100644 index 0000000000000000000000000000000000000000..490cfde1d2a01e049c55cf458600abfc17107cdb GIT binary patch literal 1455 zcmV;g1yK5lP)Px)X-PyuRCr$Po$YZPAq#4)3`YRl#Sp3H~^FtP;UNszyYAFfO7N40}g;O1)OuAjWOSE zSHBozK8!JcjWMs*TKIXJV@nV4;{Upd{xwnh#ajEijruo>*$zNOe9-;FTKjYW2r1AI zA6orut^Fp{yvS|0@c@MQAkee5_Dw`7p_^_204e@<)(=dV2XJNamj`fl@s|eh2;wgb z;8Db18Ni=Rl9i4_AoC4J$bR4bf82k!*2d=ujbmuHU9J;$&SAvIe(O-8cwcpRx*`CF zRk!mzfN=-1Qn})h0K~ii#M=rWRs_Hqf?7_Z4q`1B-N0D!R0 zxgW+DC^y#*m9)JeN7ymE(o|dXH%%+#Kmjr->09Fgdx9m{U=Mn$`pa4+p0e!03{Tn0+-v|6@q_oV zh*EOpDB1eNSy*d?qv0@@`yQMCN<}nwx5dO($~lM`Ikx35EC34DA2eWlSc<5<*LnzG zRJlBX%?r2y)Jn)tk{xodus!ED42#9H$p9Li7Ry8Sh{k1-IOFr{qZ&E}WNge5wprUJ?*RA&E%7aj}`Ln{C-=><~dcM1TJ z@~pCv7n_&z_+bFP8hV-4a4qnaQuJ^o6xyT$C{+h96^0s1Q-{Vmp0hgt{-TG9`eX1HY^qo@L?#o z1;FrBotE{t#P@qhtoM>$1OZCZpYHCp*-qw&*Hqa;4{INwWv!g;BE%QTYuG{%rx>&E zg$O(FNFsgN!vjEUQ6MHh>nNz#YQ5FLZdS0TCtIh6eGbICz~NRRmap0eWD=Zd3wx2f zqXd1(wVswiObwG@EJW@8Ad!mAELQ&UWqIM%itZq{Nd(}&RPy+2uL38ktB zq~y8E^QlwoYovyPs-oCl14kzKDl z0F=oc00)4w0?N%F4>$mn6;N*ec)$UmtblU!#{&)kWd)R*{|}2huzwZtwp9QC002ov JPDHLkV1f?xm;V3& literal 0 HcmV?d00001 diff --git a/assets/rotate_right.png b/assets/rotate_right.png new file mode 100644 index 0000000000000000000000000000000000000000..bc4540c1919d1cd31bd5b38b4b2e3dfb6358ab02 GIT binary patch literal 1484 zcmV;-1vC1IP)Px)hDk(0RCr$PodI$dAq+<0B-)eMoZ9y4Zv6dI-Syy{ zdvVS^JLev~_wfC4pXDqd6F(Ol#J^h+!ioTXIp;o}boC`6tO(%8o7=fXe9dwYRs`_H zIroil_Dexn5dgTK&v6jOI4lEUIRKz|2;njimIQDm2+IPv3WTKrTmixo0HV(azYkv@ z;>eqRh9C3q(Sg}}e>2-uc}f78;GYW7POY9#-uwI7n|fY94S+1Nfk_rN(UNNMGMg1l z0RYMGNX0(A-+MpBGpELxHfE&o@qwSc_jg^$HWq3EAcO!e2GR*6CB0*cA$KP{(G_%}kd;X9ph$#{X4{N%pn>qRxX1UXg z-_-)5+kxdn>QfySGRs{;{N4ac18!4VAcO#839O1?pJl}F27nRmfUjq}oLuR*Fg9F8 z{4M|(alWXB5*8pTw=^>Y*Vkj)i|RapF*u4vI;o>7dMXKhGT`T%0RGHP-ttM0wdG*f zYXhMC{)1M-<3J2^iVs?4o(Y5u_^QCw0E7^z?IP9Dj#dwoltZ{vwdc_UfbLkEEeFqq zQgEqacd9lB7ka-+L%E`9URRiNs4VWNSO5_ks+p>$ur+1%?p$I3y&G5~R*!27Rv_+R z8UV1!N8Gp`g0_@NbTY9U(g1+XOqccjCvyQ@(q zu-P9DQ*fe+HUMK75&+o5q&Xxr*21Lv4ZuY9f0b?k3j^4LumU`Z1-#oZR6+N@MQi|- zY98KYOZ!@z&nl7tz)}&HS%^7f6GAv9AZ@atz0$Ll9dhX9J$B_uEPz@iy5wBor1z%| z&^-HZ1^_h^>DkNWg9@7u#%Dt#04x%^+&DnEAJMZ)E#Td_p&sr>yeJ>709%&4%)(4z zi(<3;(E@;lqvFMip&<@Y8TG=6boo`}f}#abZfJQcC=9;M(NE$n1`r`&d7+`ya=tu1PRmXr- zSixkM91l(=V4{iFv2&?eKn8(&k$RaEZL{YAi-s2h;Yb`9&(z+KR2>5j4aQs%T(%s9 zo-R?{0vJ(If>3opM^6zf%kBaoLg1WouKLomrI4+4cLTtL0GGkROb}HxFYC2Fe73wu zp32o)KIgu!04gA;9|1r;JaTBD$%L^xfCvFwH#q9l6$In7Mz#2!)BC$bL0HAn+^Og& zzcrnZ__Os`n?qDq`hySY^5H9aLoA?CjC32oNZUV(2l-`D7+3Th{B + + "TopLeft" + TopCenter -> "TopCenter" + TopRight -> "TopRight" + CenterLeft -> "CenterLeft" + Center -> "Center" + CenterRight -> "CenterRight" + BottomLeft -> "BottomLeft" + BottomCenter -> "BottomCenter" + BottomRight -> "BottomRight" + else -> throw IllegalArgumentException("Unknown anchor point: $anchor") + } + } + + fun getFromName(name: String): Vec2 { + return when (name) { + "TopLeft" -> TopLeft + "TopCenter" -> TopCenter + "TopRight" -> TopRight + "CenterLeft" -> CenterLeft + "Center" -> Center + "CenterRight" -> CenterRight + "BottomLeft" -> BottomLeft + "BottomCenter" -> BottomCenter + "BottomRight" -> BottomRight + else -> throw IllegalArgumentException("Unknown anchor point: $name") + } + } + } \ No newline at end of file diff --git a/src/com/reco1l/framework/math/Vec2.kt b/src/com/reco1l/framework/math/Vec2.kt index 283e30167..d375d571b 100644 --- a/src/com/reco1l/framework/math/Vec2.kt +++ b/src/com/reco1l/framework/math/Vec2.kt @@ -48,6 +48,13 @@ data class Vec2( override fun toString() = "Vector2($x, $y)" + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Vec2) return false + + return x == other.x && y == other.y + } + companion object { val Zero = Vec2() diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 78631879c..b85019944 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -1,71 +1,50 @@ package com.reco1l.osu.hud +import android.util.Log import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes -import com.reco1l.andengine.attachTo import com.reco1l.andengine.container.Container -import com.reco1l.andengine.shape.Line -import com.reco1l.framework.ColorARGB -import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.data.HUDElementSkinData -import com.reco1l.osu.hud.data.HUDSkinData +import com.reco1l.osu.hud.editor.HUDElementSelector import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDComboCounter -import com.reco1l.osu.hud.elements.HUDElement import com.reco1l.osu.hud.elements.HUDPieSongProgress import com.reco1l.osu.hud.elements.HUDScoreCounter -import com.reco1l.osu.hud.elements.create +import com.reco1l.osu.updateThread import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.hud.* import org.anddev.andengine.entity.IEntity -import org.anddev.andengine.input.touch.TouchEvent +import org.json.JSONObject import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.GlobalManager +import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* +import ru.nsu.ccfit.zuev.skins.OsuSkin +import java.io.File +import kotlin.reflect.full.primaryConstructor class GameplayHUD( private val statistics: StatisticV2, private val gameScene: GameScene ) : Container() { - - /** - * The layout data for the HUD. - */ - var skinData: HUDSkinData = HUDSkinData.Default - set(value) { - if (field != value) { - onSkinDataChange(value) - field = value - } - } - /** - * Whether the HUD is in edit mode or not. + * The currently selected element. */ - var isInEditMode = true - set(value) { - if (field != value) { - onEditModeChange(value) - field = value - } - } - var selected: HUDElement? = null set(value) { if (field != value) { field = value mChildren?.fastForEach { - (it as? HUDElement)?.isSelected = it == value + (it as? HUDElement)?.onSelectionStateChange(it == value) } } } - private var elementSelector: HUDElementSelector? = null + private var isInEditMode = false - private var elementProperties: HUDElementProperties? = null + private var elementSelector: HUDElementSelector? = null init { @@ -79,49 +58,59 @@ class GameplayHUD( autoSizeAxes = Axes.None setSize(Config.getRES_WIDTH().toFloat(), Config.getRES_HEIGHT().toFloat()) - - onSkinDataChange(skinData) - onEditModeChange(isInEditMode) } + /** + * Adds an element to the HUD. + */ fun addElement(data: HUDElementSkinData, inEditMode: Boolean = isInEditMode) { - val element = data.type.create() - element.elementData = data + val element = data.type.primaryConstructor!!.call() attachChild(element) - addAnchorNodeLine(element) - - element.isInEditMode = inEditMode + element.setSkinData(data) + element.setEditMode(inEditMode) } - private fun onEditModeChange(value: Boolean) { + //region Skinning - mChildren?.forEach { - (it as? HUDElement)?.isInEditMode = value - } + fun saveToSkinJSON() { - val parent = parent!! + val data = getSkinData() - if (value) { - elementSelector = HUDElementSelector(this) attachTo parent - elementProperties = HUDElementProperties(this) attachTo parent + val jsonFile = File(GlobalManager.getInstance().skinNow, "skin.json") + val json: JSONObject - parent.registerTouchArea(elementSelector) - parent.registerTouchArea(elementProperties) + if (jsonFile.exists()) { + json = JSONObject(jsonFile.reader().readText()) } else { - parent.unregisterTouchArea(elementSelector) - parent.unregisterTouchArea(elementProperties) + jsonFile.createNewFile() + json = JSONObject() + } - elementSelector?.detachSelf() - elementSelector = null + json.put("HUD", HUDSkinData.writeToJSON(data)) + jsonFile.writeText(json.toString()) - elementProperties?.detachSelf() - elementProperties = null - } + OsuSkin.get().hudSkinData = data + } + + /** + * Saves the skin data of the HUD. + */ + fun getSkinData(): HUDSkinData { + return HUDSkinData( + elements = mChildren?.filterIsInstance() + ?.map { it.getSkinData() } + ?: emptyList() + ) } - private fun onSkinDataChange(layoutData: HUDSkinData) { + /** + * Sets the skin data of the HUD. + */ + fun setSkinData(layoutData: HUDSkinData) { + + Log.i("GameplayHUD", "Setting skin data: $layoutData<") mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) @@ -129,10 +118,13 @@ class GameplayHUD( // applying default layout. layoutData.elements.forEach { data -> addElement(data) } - applyDefaultLayout() + if (layoutData == HUDSkinData.Default) { + applyDefaultLayout() + } + } - // Second pass: Apply custom element data that will override the default layout if any. - mChildren?.forEach { (it as? HUDElement)?.onSkinDataChange(it.elementData) } + private inline fun getFirstOf() : T? { + return mChildren?.firstOrNull { it is T } as? T } private fun applyDefaultLayout() { @@ -171,30 +163,37 @@ class GameplayHUD( comboCounter?.setPosition(10f, -10f) comboCounter?.setScale(1.28f) } + //endregion - private fun addAnchorNodeLine(element: HUDElement) { + //region Elements events + fun setEditMode(value: Boolean) { + isInEditMode = value - val pointOnParent = Vec2( - drawWidth * element.anchor.x, - drawHeight * element.anchor.y - ) + if (value) { + ResourceManager.getInstance().loadHighQualityAsset("delete", "delete.png") + ResourceManager.getInstance().loadHighQualityAsset("expand", "expand.png") + ResourceManager.getInstance().loadHighQualityAsset("rotate_left", "rotate_left.png") + ResourceManager.getInstance().loadHighQualityAsset("rotate_right", "rotate_right.png") - val pointFromChild = Vec2( - element.drawX + element.drawWidth * element.origin.x, - element.drawY + element.drawHeight * element.origin.y - ) + elementSelector = HUDElementSelector(this) + + parent!!.attachChild(elementSelector) + parent!!.registerTouchArea(elementSelector) - element.nodeLine = Line().apply { - fromPoint = pointFromChild - toPoint = pointOnParent - color = ColorARGB(0xFFF27272) - lineWidth = 10f - isVisible = false + } else { + updateThread { + parent!!.detachChild(elementSelector) + parent!!.unregisterTouchArea(elementSelector) + + elementSelector = null + } } - attachChild(element.nodeLine!!) + updateThread { + mChildren?.filterIsInstance()?.forEach { it.setEditMode(value) } + } } - + //endregion //region Entity events override fun onManagedUpdate(pSecondsElapsed: Float) { @@ -204,14 +203,6 @@ class GameplayHUD( elementSelector?.onGameplayUpdate(gameScene, statistics, pSecondsElapsed) super.onManagedUpdate(pSecondsElapsed) } - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (super.onAreaTouched(event, localX, localY)) { - return true - } - selected = null - return false - } //endregion //region Gameplay Events @@ -231,11 +222,6 @@ class GameplayHUD( //endregion - private inline fun getFirstOf() : T? { - return mChildren?.firstOrNull { it is T } as? T - } - - override fun getParent(): HUD? { // Nullable because during initialization the parent is not set yet. return super.getParent() as? HUD diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt new file mode 100644 index 000000000..0964f312c --- /dev/null +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -0,0 +1,265 @@ +package com.reco1l.osu.hud + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.originOffsetX +import com.reco1l.andengine.originOffsetY +import com.reco1l.andengine.shape.Line +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.framework.ColorARGB +import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.hud.editor.HUDElementToolbar +import com.reco1l.osu.hud.elements.HUDAccuracyCounter +import com.reco1l.osu.hud.elements.HUDComboCounter +import com.reco1l.osu.hud.elements.HUDHealthBar +import com.reco1l.osu.hud.elements.HUDPPCounter +import com.reco1l.osu.hud.elements.HUDPieSongProgress +import com.reco1l.osu.hud.elements.HUDScoreCounter +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.ResourceManager +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import kotlin.math.abs + + +/** + * List all the elements that can be added to the HUD. + */ +val HUDElements = listOf( + HUDAccuracyCounter::class, + HUDComboCounter::class, + HUDHealthBar::class, + HUDPieSongProgress::class, + HUDPPCounter::class, + HUDScoreCounter::class, +) + + +abstract class HUDElement : Container() { + + /** + * Returns the name of this element. + */ + val name: String + get() = this::class.simpleName!!.replace("HUD", "").replace("([a-z])([A-Z])".toRegex(), "$1 $2") + + + private var toolbar: HUDElementToolbar? = null + + private var connectionLine: Line? = null + + private var isInEditMode = false + + + //region Skinning + + open fun setSkinData(data: HUDElementSkinData?) { + if (data != null) { + anchor = data.anchor + origin = data.origin + rotation = data.rotation + setScale(data.scale) + setPosition(data.position.x, data.position.y) + } + } + + open fun getSkinData() = HUDElementSkinData( + type = this::class, + anchor = anchor, + origin = origin, + scale = scaleX, // Scale is uniform currently. + position = Vec2(x, y), + rotation = rotation + ) + + //endregion + + //region Element events + + open fun setEditMode(value: Boolean) { + isInEditMode = value + + if (value) { + background = HUDElementBackground() + toolbar = HUDElementToolbar(this) + + parent!!.attachChild(toolbar!!) + } else { + connectionLine?.detachSelf() + toolbar?.detachSelf() + + connectionLine = null + background = null + toolbar = null + } + } + + open fun onSelectionStateChange(isSelected: Boolean) { + + (background as HUDElementBackground).isSelected = isSelected + toolbar?.isVisible = isSelected + + if (isSelected) { + updateConnectionLine() + connectionLine?.isVisible = true + } else { + connectionLine?.isVisible = false + } + } + + //endregion + + //region Entity events + + private var initialX = 0f + private var initialY = 0f + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (isInEditMode) { + val parentLocalX = drawX + localX + val parentLocalY = drawY + localY + + if (event.isActionDown) { + parent!!.selected = this + + initialX = parentLocalX + initialY = parentLocalY + return true + } + + if (event.isActionMove) { + val deltaX = parentLocalX - initialX + val deltaY = parentLocalY - initialY + + setPosition(x + deltaX, y + deltaY) + findNearestAnchorPoint() + updateConnectionLine() + + initialX = parentLocalX + initialY = parentLocalY + return true + } + + } + return false + } + + override fun invalidateTransformations() { + super.invalidateTransformations() + + toolbar?.invalidateTransformations() + } + + private fun findNearestAnchorPoint() { + + val anchors = floatArrayOf(0f, 0.5f, 1f) + + val nearestAnchor = Vec2( + anchors.minBy { abs(drawX - originOffsetX - parent!!.drawWidth * it) }, + anchors.minBy { abs(drawY - originOffsetY - parent!!.drawHeight * it) } + ) + + if (nearestAnchor.x != anchor.x) { + x = -x + } + + if (nearestAnchor.y != anchor.y) { + y = -y + } + + anchor = nearestAnchor + } + + private fun updateConnectionLine() { + + val pointOnParent = Vec2( + parent!!.drawWidth * anchor.x, + parent!!.drawHeight * anchor.y + ) + + val pointOnChild = Vec2( + drawX + drawWidth * origin.x, + drawY + drawHeight * origin.y + ) + + if (connectionLine == null) { + connectionLine = Line().apply { + fromPoint = pointOnParent + toPoint = pointOnChild + color = ColorARGB(0xFFF27272) + lineWidth = 10f + } + parent!!.attachChild(connectionLine!!) + } else { + connectionLine!!.fromPoint = pointOnParent + connectionLine!!.toPoint = pointOnChild + } + } + + //endregion + + //region Gameplay Events + open fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) = Unit + open fun onNoteHit(statistics: StatisticV2) = Unit + open fun onBreakStateChange(isBreak: Boolean) = Unit + //endregion + + + override fun getParent(): GameplayHUD? { + return super.getParent() as? GameplayHUD + } + + + //region Edit mode + inner class HUDElementBackground : Container() { + + + var isSelected = false + set(value) { + background!!.alpha = if (value) 0.5f else 0.15f + nameText.isVisible = value + field = value + } + + + private val nameText = ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + color = ColorARGB(0xFFF27272) + text = this@HUDElement.name + isVisible = false + } + + + init { + attachChild(nameText) + + background = RoundedBox().apply { + color = ColorARGB(0x29F27272) + alpha = 0.25f + } + } + + override fun onManagedUpdate(pSecondsElapsed: Float) { + + // Switch the text position according to the element's position. + if (this@HUDElement.drawY - drawHeight <= 0f) { + nameText.anchor = Anchor.BottomLeft + nameText.origin = Anchor.TopLeft + } else { + nameText.anchor = Anchor.TopLeft + nameText.origin = Anchor.BottomLeft + } + + // The element might contain scale transformations. Those transformations are also applied + // to the background we might want to scale the text back to its original size. + nameText.setScale(1f / this@HUDElement.scaleX, 1f / this@HUDElement.scaleY) + + // Same for the corner radius of the background. + (background as RoundedBox).cornerRadius = 6f * (1f / this@HUDElement.scaleX) + } + + } + //endregion +} diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt new file mode 100644 index 000000000..24a542b12 --- /dev/null +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -0,0 +1,106 @@ +package com.reco1l.osu.hud + +import com.reco1l.andengine.Anchor +import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.hud.elements.HUDAccuracyCounter +import com.reco1l.osu.hud.elements.HUDComboCounter +import com.reco1l.osu.hud.elements.HUDHealthBar +import com.reco1l.osu.hud.elements.HUDPieSongProgress +import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.toolkt.data.putObject +import org.json.JSONArray +import kotlin.reflect.KClass + +data class HUDSkinData(val elements: List) { + + companion object { + + /** + * The default layout data for the HUD. + * Based on the default skin layout from osu!stable. + */ + @JvmField + val Default = HUDSkinData( + listOf( + HUDElementSkinData(type = HUDAccuracyCounter::class), + HUDElementSkinData(type = HUDComboCounter::class), + HUDElementSkinData(type = HUDPieSongProgress::class), + HUDElementSkinData(type = HUDHealthBar::class), + HUDElementSkinData(type = HUDScoreCounter::class) + ) + ) + + + @JvmStatic + fun writeToJSON(data: HUDSkinData) = JSONArray().apply { + data.elements.forEach { + putObject { + put("type", it.type.simpleName) + put("x", it.position.x) + put("y", it.position.y) + put("anchor", Anchor.getName(it.anchor)) + put("origin", Anchor.getName(it.origin)) + put("scale", it.scale) + put("rotation", it.rotation) + } + } + } + + @JvmStatic + fun readFromJSON(json: JSONArray) = HUDSkinData( + elements = MutableList(json.length()) { i -> + + val element = json.getJSONObject(i) + + HUDElementSkinData( + type = HUDElements.first { it.simpleName == element.getString("type") }, + position = Vec2( + element.optDouble("x", 0.0).toFloat(), + element.optDouble("y", 0.0).toFloat(), + ), + anchor = Anchor.getFromName(element.optString("anchor", "TopLeft")), + origin = Anchor.getFromName(element.optString("origin", "TopLeft")), + scale = element.getDouble("scale").toFloat(), + rotation = element.getDouble("rotation").toFloat() + ) + } + + ) + + + } + +} + +data class HUDElementSkinData( + + /** + * The type of the element. + */ + val type: KClass, + + /** + * The offset of the element from the constraint. + */ + val position: Vec2 = Vec2.Zero, + + /** + * The anchor of the element. + */ + val anchor: Vec2 = Anchor.TopLeft, + + /** + * The origin of the element. + */ + val origin: Vec2 = Anchor.TopLeft, + + /** + * The scale applied to the element. + */ + val scale: Float = 1f, + + /** + * The rotation of the element. + */ + val rotation: Float = 0f +) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/data/HUDSkinData.kt b/src/com/reco1l/osu/hud/data/HUDSkinData.kt deleted file mode 100644 index 7d45e8d76..000000000 --- a/src/com/reco1l/osu/hud/data/HUDSkinData.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.reco1l.osu.hud.data - -import com.reco1l.andengine.Anchor -import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.elements.HUDAccuracyCounter -import com.reco1l.osu.hud.elements.HUDComboCounter -import com.reco1l.osu.hud.elements.HUDElement -import com.reco1l.osu.hud.elements.HUDHealthBar -import com.reco1l.osu.hud.elements.HUDPieSongProgress -import com.reco1l.osu.hud.elements.HUDScoreCounter -import kotlin.reflect.KClass - -data class HUDSkinData(val elements: List) { - - companion object { - - /** - * The default layout data for the HUD. - * Based on the default skin layout from osu!stable. - */ - val Default = HUDSkinData( - listOf( - HUDElementSkinData(type = HUDAccuracyCounter::class), - HUDElementSkinData(type = HUDComboCounter::class), - HUDElementSkinData(type = HUDPieSongProgress::class), - HUDElementSkinData(type = HUDHealthBar::class), - HUDElementSkinData(type = HUDScoreCounter::class) - ) - ) - - } - -} - -data class HUDElementSkinData( - - /** - * The type of the element. - */ - val type: KClass, - - /** - * The layout of the element. - */ - val layout: HUDElementLayoutData? = null, - - /** - * The set of settings for the element. - */ - val settings: Map = mapOf(), -) - -data class HUDElementLayoutData( - - /** - * The offset of the element from the constraint. - */ - val position: Vec2 = Vec2.Zero, - - /** - * The anchor of the element. - */ - val anchor: Vec2 = Anchor.TopLeft, - - /** - * The origin of the element. - */ - val origin: Vec2 = Anchor.TopLeft, - - /** - * The scale applied to the element. - */ - val scale: Float = 1f, -) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt new file mode 100644 index 000000000..45e335292 --- /dev/null +++ b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt @@ -0,0 +1,94 @@ +package com.reco1l.osu.hud.editor + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.getPaddedHeight +import com.reco1l.andengine.getPaddedWidth +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.framework.ColorARGB +import com.reco1l.framework.math.Vec4 +import com.reco1l.osu.hud.GameplayHUD +import com.reco1l.osu.hud.HUDElement +import com.reco1l.osu.hud.HUDElementSkinData +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.ResourceManager +import kotlin.math.abs + +class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Container() { + + + init { + width = HUDElementSelector.SELECTOR_WIDTH - 16f * 2 + height = 120f + padding = Vec4(12f) + scaleCenterX = 0.5f + scaleCenterY = 0.5f + + background = RoundedBox().apply { + color = ColorARGB(0xFF363653) + cornerRadius = 12f + } + + attachChild(ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + anchor = Anchor.BottomLeft + origin = Anchor.BottomLeft + text = element.name + color = ColorARGB.White + }) + + attachChild(element) + element.setSkinData(HUDElementSkinData(element::class)) + + // Scaling the element inside the box + + if (element.drawHeight > getPaddedHeight()) { + element.setScale(getPaddedHeight() / element.drawHeight) + } + + if (element.drawWidth > getPaddedWidth()) { + element.setScale(getPaddedWidth() / element.drawWidth) + } + } + + //region Input handling + private var initialX = 0f + private var initialY = 0f + private var initialTime = 0L + private var wasMoved = false + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (event.isActionDown) { + initialX = localX + initialY = localY + initialTime = System.currentTimeMillis() + + clearEntityModifiers() + scaleTo(0.9f, 0.1f) + return true + } + + if (event.isActionMove) { + clearEntityModifiers() + scaleTo(1f, 0.1f) + } + + if (event.isActionUp) { + clearEntityModifiers() + scaleTo(1f, 0.1f) + + wasMoved = abs(localX - initialX) > 1f && abs(localY - initialY) > 1f + + if (!wasMoved && System.currentTimeMillis() - initialTime > 50) { + hud.addElement(HUDElementSkinData(element::class)) + return false + } + } + + return false + } + //endregion + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt similarity index 50% rename from src/com/reco1l/osu/hud/HUDElementSelector.kt rename to src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index 8600ee3c0..813557306 100644 --- a/src/com/reco1l/osu/hud/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -1,70 +1,63 @@ -package com.reco1l.osu.hud +package com.reco1l.osu.hud.editor import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes -import com.reco1l.andengine.attachTo import com.reco1l.andengine.container.Container import com.reco1l.andengine.container.LinearContainer import com.reco1l.andengine.container.Orientation import com.reco1l.andengine.container.ScrollableContainer -import com.reco1l.andengine.getPaddedHeight -import com.reco1l.andengine.getPaddedWidth import com.reco1l.andengine.shape.Box -import com.reco1l.andengine.shape.Circle import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec4 -import com.reco1l.osu.hud.data.HUDElementSkinData -import com.reco1l.osu.hud.elements.HUDAccuracyCounter -import com.reco1l.osu.hud.elements.HUDComboCounter -import com.reco1l.osu.hud.elements.HUDElement -import com.reco1l.osu.hud.elements.HUDHealthBar -import com.reco1l.osu.hud.elements.HUDPPCounter -import com.reco1l.osu.hud.elements.HUDPieSongProgress -import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.osu.hud.GameplayHUD +import com.reco1l.osu.hud.HUDElement +import com.reco1l.osu.hud.HUDElements import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 -import kotlin.math.abs +import kotlin.reflect.full.primaryConstructor class HUDElementSelector(private val hud: GameplayHUD) : Container() { - private val elements = createAllElements() + private val elements = HUDElements.map { it.primaryConstructor!!.call() } init { - relativeSizeAxes = Axes.Y height = 1f + x = -SELECTOR_WIDTH // The button to show/hide the element selector - object : Container() { + attachChild(object : Container() { init { background = RoundedBox().apply { cornerRadius = BUTTON_RADIUS color = ColorARGB(0xFF181825) } - width = BUTTON_WIDTH + BUTTON_RADIUS - height = 150f + + setSize(BUTTON_WIDTH, 150f) x = SELECTOR_WIDTH - BUTTON_RADIUS + anchor = Anchor.CenterLeft origin = Anchor.CenterLeft - ExtendedText().apply { + attachChild(ExtendedText().apply { rotation = -90f anchor = Anchor.Center origin = Anchor.Center font = ResourceManager.getInstance().getFont("smallFont") text = "Elements" + x = BUTTON_RADIUS / 2 - } attachTo this + }) } override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { @@ -89,35 +82,33 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container() { return false } - } attachTo this + }) - ScrollableContainer().apply { + attachChild(ScrollableContainer().apply { scrollAxes = Axes.Y relativeSizeAxes = Axes.Y height = 1f width = SELECTOR_WIDTH - indicatorY!!.width = 4f background = Box().apply { color = ColorARGB(0xFF1E1E2E) } - val linearContainer = LinearContainer().apply { + attachChild(LinearContainer().apply { relativeSizeAxes = Axes.X width = 1f padding = Vec4(16f) spacing = 12f orientation = Orientation.Vertical - } attachTo this - elements.forEach { element -> - HUDElementPreview(element, hud) attachTo linearContainer - } - - } attachTo this + elements.forEach { element -> + attachChild(HUDElementPreview(element, hud)) + } + }) + }) } @@ -144,103 +135,11 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container() { companion object { - const val SELECTOR_WIDTH = 300f - const val BUTTON_WIDTH = 48f - const val BUTTON_RADIUS = 12f - - fun createAllElements(): List { - return listOf( - HUDAccuracyCounter(), - HUDComboCounter(), - HUDHealthBar(), - HUDPieSongProgress(), - HUDPPCounter(), - HUDScoreCounter() - ) - } - } } -class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Container() { - - - init { - width = HUDElementSelector.SELECTOR_WIDTH - 16f * 2 - height = 120f - padding = Vec4(12f) - scaleCenterX = 0.5f - scaleCenterY = 0.5f - - background = RoundedBox().apply { - color = ColorARGB(0xFF363653) - cornerRadius = 12f - } - - ExtendedText().apply { - font = ResourceManager.getInstance().getFont("smallFont") - anchor = Anchor.BottomLeft - origin = Anchor.BottomLeft - text = element.name - color = ColorARGB.White - } attachTo this - - element attachTo this - element.onSkinDataChange(HUDElementSkinData(element::class)) - - // Scaling the element inside the box - - if (element.drawHeight > getPaddedHeight()) { - element.setScale(getPaddedHeight() / element.drawHeight) - } - - if (element.drawWidth > getPaddedWidth()) { - element.setScale(getPaddedWidth() / element.drawWidth) - } - } - - //region Input handling - private var initialX = 0f - private var initialY = 0f - private var initialTime = 0L - private var wasMoved = false - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - - if (event.isActionDown) { - initialX = localX - initialY = localY - initialTime = System.currentTimeMillis() - - clearEntityModifiers() - scaleTo(0.9f, 0.1f) - return true - } - - if (event.isActionMove) { - clearEntityModifiers() - scaleTo(1f, 0.1f) - } - - if (event.isActionUp) { - clearEntityModifiers() - scaleTo(1f, 0.1f) - - wasMoved = abs(localX - initialX) > 1f && abs(localY - initialY) > 1f - - if (!wasMoved && System.currentTimeMillis() - initialTime > 50) { - hud.addElement(HUDElementSkinData(element::class)) - return false - } - } - - return false - } - //endregion - -} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt new file mode 100644 index 000000000..d6bf13e08 --- /dev/null +++ b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt @@ -0,0 +1,89 @@ +package com.reco1l.osu.hud.editor + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.Axes +import com.reco1l.andengine.container.Container +import com.reco1l.andengine.container.LinearContainer +import com.reco1l.andengine.container.Orientation +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.sprite.ExtendedSprite +import com.reco1l.framework.ColorARGB +import com.reco1l.osu.hud.HUDElement +import com.reco1l.osu.updateThread +import org.anddev.andengine.input.touch.TouchEvent +import ru.nsu.ccfit.zuev.osu.ResourceManager + +class HUDElementToolbar(private val element: HUDElement) : LinearContainer() { + + init { + orientation = Orientation.Horizontal + isVisible = false + spacing = 4f + + attachChild(createToolbarButton("delete", ColorARGB(0xFF260000)) { + updateThread { + element.parent?.detachChild(element) + } + }) + + attachChild(createToolbarButton("rotate_left", ColorARGB(0xFF181825)) { + element.rotation -= 90f + }) + + attachChild(createToolbarButton("rotate_right", ColorARGB(0xFF181825)) { + element.rotation += 90f + }) + + } + + private fun createToolbarButton(texture: String, back: ColorARGB, action: () -> Unit) = object : Container() { + + init { + setSize(46f, 46f) + + background = RoundedBox().apply { + cornerRadius = 12f + color = back + } + + attachChild(ExtendedSprite().apply { + textureRegion = ResourceManager.getInstance().getTexture(texture) + anchor = Anchor.Center + origin = Anchor.Center + relativeSizeAxes = Axes.Both + setSize(0.9f, 0.9f) + }) + + } + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (event.isActionUp) { + action() + return true + } + return false + } + + override fun onManagedUpdate(pSecondsElapsed: Float) { + + x = element.drawX + element.drawWidth / 2 - drawWidth / 2 + + if (element.drawY < drawHeight) { + y = element.drawY + element.drawHeight + 4f + } else { + y = element.drawY - drawHeight - 4f + } + + super.onManagedUpdate(pSecondsElapsed) + } + } + + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (!isVisible) { + return false + } + return super.onAreaTouched(event, localX, localY) + } + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt index 49a6cbfa9..c9f7f7b33 100644 --- a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt @@ -1,5 +1,6 @@ package com.reco1l.osu.hud.elements +import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 diff --git a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt index 41ca682a2..a3de6c4cb 100644 --- a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt @@ -1,9 +1,10 @@ package com.reco1l.osu.hud.elements import com.edlplan.framework.easing.* +import com.reco1l.andengine.Anchor import com.reco1l.andengine.modifier.OnModifierFinished import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.data.HUDElementSkinData +import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.* import ru.nsu.ccfit.zuev.osu.game.GameScene @@ -13,14 +14,6 @@ import ru.nsu.ccfit.zuev.skins.* class HUDComboCounter : HUDElement() { - override var origin: Vec2 - get() = super.origin - set(value) { - super.origin = value - popOutCount?.origin = value - displayedCountTextSprite.origin = value - } - private val popOutCount = if (Config.isAnimateComboText()) SpriteFont(OsuSkin.get().comboPrefix).also { @@ -28,9 +21,6 @@ class HUDComboCounter : HUDElement() { it.text = "0x" it.spacing = -OsuSkin.get().comboOverlap - // In stable, the bigger pop out scales a bit to the left - it.translationX = -3f - attachChild(it) } else null @@ -92,18 +82,17 @@ class HUDComboCounter : HUDElement() { } - override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { setCombo(statistics.combo) } - override fun onSkinDataChange(data: HUDElementSkinData?) { - super.onSkinDataChange(data) + override fun onManagedUpdate(pSecondsElapsed: Float) { + + popOutCount?.origin = anchor + displayedCountTextSprite.origin = anchor + - displayedCountTextSprite.anchor = anchor - displayedCountTextSprite.origin = origin - popOutCount?.origin = origin - popOutCount?.anchor = anchor + super.onManagedUpdate(pSecondsElapsed) } diff --git a/src/com/reco1l/osu/hud/elements/HUDElement.kt b/src/com/reco1l/osu/hud/elements/HUDElement.kt deleted file mode 100644 index ad413504f..000000000 --- a/src/com/reco1l/osu/hud/elements/HUDElement.kt +++ /dev/null @@ -1,233 +0,0 @@ -package com.reco1l.osu.hud.elements - -import com.reco1l.andengine.Anchor -import com.reco1l.andengine.Axes -import com.reco1l.andengine.container.Container -import com.reco1l.andengine.shape.Line -import com.reco1l.andengine.shape.RoundedBox -import com.reco1l.andengine.text.ExtendedText -import com.reco1l.framework.ColorARGB -import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.GameplayHUD -import com.reco1l.osu.hud.data.HUDElementSkinData -import org.anddev.andengine.input.touch.TouchEvent -import ru.nsu.ccfit.zuev.osu.ResourceManager -import ru.nsu.ccfit.zuev.osu.game.GameScene -import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 -import kotlin.math.abs -import kotlin.reflect.KClass - - -fun KClass.create(): T { - return constructors.first().call() -} - -abstract class HUDElement : Container() { - - /** - * The HUD element data for this element. This property can only be set once. - */ - var elementData: HUDElementSkinData? = null - set(value) { - if (field == null) { - field = value - } else { - throw IllegalStateException("The layout data for this element has already been set.") - } - } - - /** - * Whether the element is in edit mode or not. - */ - var isInEditMode = false - set(value) { - if (field != value) { - onEditModeChange(value) - field = value - } - } - - /** - * Whether the element is selected or not. - */ - var isSelected = false - set(value) { - if (field != value) { - val background = background as HUDElementUnderlay - - if (value) { - background.select() - } else { - background.unselect() - } - field = value - } - } - - /** - * The line that connects this element to the parent's anchor - */ - var nodeLine: Line? = null - - /** - * Returns the name of this element. - */ - val name: String - get() = this::class.simpleName!!.substring(3).replace("([a-z])([A-Z])".toRegex(), "$1 $2") - - - private var initialX = 0f - private var initialY = 0f - - - open fun onEditModeChange(value: Boolean) { - background = if (value) HUDElementUnderlay(this) else null - } - - - open fun onSkinDataChange(data: HUDElementSkinData?) { - - val layout = data?.layout ?: return - - anchor = layout.anchor - origin = layout.origin - - setScale(layout.scale) - setPosition(layout.position.x, layout.position.y) - } - - - private fun calculateNearestAnchor(deltaX: Float, deltaY: Float) { - - val anchors = floatArrayOf(0f, 0.5f, 1f) - - val originX = drawX + drawWidth * origin.x + deltaX - val originY = drawY + drawHeight * origin.y + deltaY - - val nearestX = anchors.minBy { abs(originX - parent!!.drawWidth * it) } - val nearestY = anchors.minBy { abs(originY - parent!!.drawHeight * it) } - - if (nearestX != anchor.x) { - x = -x - } - - if (nearestY != anchor.y) { - y = -y - } - - anchor = Vec2(nearestX, nearestY) - } - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - - if (isInEditMode) { - val parentLocalX = drawX + localX - val parentLocalY = drawY + localY - - if (event.action == TouchEvent.ACTION_DOWN) { - parent!!.selected = this - initialX = parentLocalX - initialY = parentLocalY - return true - } - - if (event.action == TouchEvent.ACTION_MOVE) { - val deltaX = parentLocalX - initialX - val deltaY = parentLocalY - initialY - - calculateNearestAnchor(deltaX, deltaY) - setPosition(x + deltaX, y + deltaY) - - nodeLine?.toPoint = Vec2( - parent!!.drawWidth * anchor.x, - parent!!.drawHeight * anchor.y - ) - - nodeLine?.fromPoint = Vec2( - drawX + drawWidth * origin.x, - drawY + drawHeight * origin.y - ) - - initialX = parentLocalX - initialY = parentLocalY - return true - } - - } - return false - } - - - //region Gameplay Events - open fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { - // Override this method to update the element with the latest gameplay data. - } - - open fun onNoteHit(statistics: StatisticV2) { - // Override this method to handle hit object hits. - } - - open fun onBreakStateChange(isBreak: Boolean) { - // Override this method to handle break state changes. - } - //endregion - - - override fun getParent(): GameplayHUD? { - return super.getParent() as? GameplayHUD - } - -} - - -class HUDElementUnderlay(private val element: HUDElement) : Container() { - - private val nameText = ExtendedText() - - - init { - nameText.font = ResourceManager.getInstance().getFont("smallFont") - nameText.color = ColorARGB(0xFFF27272) - nameText.text = element.name - nameText.isVisible = false - attachChild(nameText) - - autoSizeAxes = Axes.None - - background = RoundedBox().apply { - color = ColorARGB(0x29F27272) - alpha = 0.25f - } - } - - override fun onManagedUpdate(pSecondsElapsed: Float) { - - if (element.drawY - drawHeight <= 0f) { - nameText.anchor = Anchor.BottomLeft - nameText.origin = Anchor.TopLeft - } else { - nameText.anchor = Anchor.TopLeft - nameText.origin = Anchor.BottomLeft - } - - // Cancel the scaling of the HUD element so the text is not affected by it, the same goes for the background. - nameText.setScale(1f / element.scaleX, 1f / element.scaleY) - - (background as RoundedBox).cornerRadius = 6f * (1f / element.scaleX) - } - - - fun select() { - background!!.color = ColorARGB(0x80F27272) - nameText.isVisible = true - element.nodeLine?.isVisible = true - } - - fun unselect() { - background!!.color = ColorARGB(0x29F27272) - nameText.isVisible = false - element.nodeLine?.isVisible = false - } - - -} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt index e910caec7..d385df684 100644 --- a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt +++ b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt @@ -7,6 +7,7 @@ import com.reco1l.andengine.shape.* import com.reco1l.andengine.sprite.* import com.reco1l.andengine.texture.* import com.reco1l.framework.* +import com.reco1l.osu.hud.HUDElement import org.anddev.andengine.opengl.texture.region.* import ru.nsu.ccfit.zuev.osu.* import ru.nsu.ccfit.zuev.osu.game.GameScene diff --git a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt index 04c8ea06c..788645ca6 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt @@ -1,5 +1,6 @@ package com.reco1l.osu.hud.elements +import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.DifficultyAlgorithm diff --git a/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt index 9495af413..792436dac 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt @@ -4,6 +4,7 @@ import androidx.annotation.IntDef import com.reco1l.andengine.* import com.reco1l.andengine.shape.* import com.reco1l.framework.* +import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.hud.elements.ProgressIndicatorType.Companion.BAR import com.reco1l.osu.hud.elements.ProgressIndicatorType.Companion.PIE import ru.nsu.ccfit.zuev.osu.game.GameScene diff --git a/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt b/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt index a80ff92bd..a5625458d 100644 --- a/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDScoreCounter.kt @@ -1,5 +1,6 @@ package com.reco1l.osu.hud.elements +import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 diff --git a/src/com/reco1l/osu/ui/SettingsFragment.kt b/src/com/reco1l/osu/ui/SettingsFragment.kt index 65c4293de..01ecc263e 100644 --- a/src/com/reco1l/osu/ui/SettingsFragment.kt +++ b/src/com/reco1l/osu/ui/SettingsFragment.kt @@ -21,6 +21,7 @@ import androidx.core.view.forEach import androidx.core.view.get import androidx.preference.CheckBoxPreference import androidx.preference.Preference +import androidx.preference.Preference.OnPreferenceClickListener import androidx.preference.SeekBarPreference import com.osudroid.resources.R.* import com.edlplan.ui.fragment.LoadingFragment @@ -49,11 +50,14 @@ import ru.nsu.ccfit.zuev.osu.LibraryManager import ru.nsu.ccfit.zuev.osu.MainActivity import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.ToastLogger +import ru.nsu.ccfit.zuev.osu.game.mods.GameMod import ru.nsu.ccfit.zuev.osu.helper.StringTable +import ru.nsu.ccfit.zuev.osu.menu.ModMenu import ru.nsu.ccfit.zuev.osu.online.OnlineManager import ru.nsu.ccfit.zuev.osuplus.R import ru.nsu.ccfit.zuev.skins.BeatmapSkinManager import java.io.File +import java.util.EnumSet import kotlin.math.max @@ -306,6 +310,26 @@ class SettingsFragment : com.edlplan.ui.fragment.SettingsFragment() { true } } + + findPreference("hud_editor")!!.apply { + + if (Multiplayer.isMultiplayer) { + isEnabled = false + return + } + + setOnPreferenceClickListener { + + if (LibraryManager.getSizeOfBeatmaps() == 0) { + ToastLogger.showText("Cannot enter HUD editor with empty beatmap library!", true) + } else { + dismiss() + ModMenu.getInstance().mod = EnumSet.of(GameMod.MOD_AUTO) + GlobalManager.getInstance().gameScene.startGame(GlobalManager.getInstance().selectedBeatmap, null, true) + } + true + } + } } diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 904be50c8..96e683ee8 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -225,6 +225,11 @@ public class GameScene implements IUpdateHandler, GameObjectListener, */ private LinearSongProgress linearSongProgress; + /** + * Whether the HUD editor mode is enabled. + */ + private boolean isHUDEditorMode = false; + // Timing @@ -643,7 +648,15 @@ public void restartGame() { startGame(null, null); } - public void startGame(final BeatmapInfo beatmapInfo, final String replayFile) { + + public void startGame(BeatmapInfo beatmapInfo, String replayFile) { + startGame(beatmapInfo, replayFile, false); + } + + public void startGame(BeatmapInfo beatmapInfo, String replayFile, boolean isHUDEditor) { + + isHUDEditorMode = isHUDEditor; + scene = new ExtendedScene(); if (Config.isEnableStoryboard()) { if (storyboardSprite == null || storyboardOverlayProxy == null) { @@ -688,7 +701,7 @@ public void startGame(final BeatmapInfo beatmapInfo, final String replayFile) { succeeded = loadGame(beatmapInfo != null ? beatmapInfo : lastBeatmapInfo, rfile, scope); if (succeeded) { - prepareScene(); + prepareScene(isHUDEditor); } } finally { if (!succeeded) { @@ -722,7 +735,7 @@ public void cancelLoading() { } } - private void prepareScene() { + private void prepareScene(boolean isHudEditor) { scene.setOnSceneTouchListener(this); if (GlobalManager.getInstance().getCamera() instanceof SmoothCamera) { SmoothCamera camera = (SmoothCamera) (GlobalManager.getInstance().getCamera()); @@ -776,6 +789,8 @@ private void prepareScene() { counterTexts.clear(); hud = new GameplayHUD(stat, this); + hud.setSkinData(OsuSkin.get().getHUDSkinData()); + hud.setEditMode(isHudEditor); var counterTextFont = ResourceManager.getInstance().getFont("smallFont"); @@ -1026,6 +1041,10 @@ public void start() { blockAreaFragment = new BlockAreaFragment(); blockAreaFragment.show(false); + + if (isHUDEditorMode) { + ToastLogger.showText("Press back to exit HUD editor mode and save.", false); + } } public RGBColor getComboColor(int num) { @@ -2201,6 +2220,18 @@ public void pause() { return; } + if (isHUDEditorMode) { + hud.setEditMode(false); + isHUDEditorMode = false; + ToastLogger.showText("Saving HUD layout...", true); + + Execution.async(() -> { + hud.saveToSkinJSON(); + ToastLogger.showText("HUD layout saved successfully.", true); + }); + return; + } + if (Multiplayer.isMultiplayer) { // Setting a delay of 300ms for the player to tap back button again. diff --git a/src/ru/nsu/ccfit/zuev/skins/OsuSkin.java b/src/ru/nsu/ccfit/zuev/skins/OsuSkin.java index 789662209..07ebe75f9 100644 --- a/src/ru/nsu/ccfit/zuev/skins/OsuSkin.java +++ b/src/ru/nsu/ccfit/zuev/skins/OsuSkin.java @@ -2,6 +2,8 @@ import androidx.annotation.NonNull; +import com.reco1l.osu.hud.HUDSkinData; + import okio.BufferedSource; import okio.Okio; import ru.nsu.ccfit.zuev.osu.RGBColor; @@ -11,6 +13,8 @@ import java.util.ArrayList; import java.util.HashMap; +import javax.annotation.Nullable; + public class OsuSkin { private static final OsuSkin skinJson = new OsuSkin(); @@ -52,6 +56,9 @@ public class OsuSkin { protected final HashMap layoutData = new HashMap<>(); protected final HashMap colorData = new HashMap<>(); + protected HUDSkinData hudSkinData = HUDSkinData.Default; + + public static OsuSkin get() { return skinJson; } @@ -199,5 +206,15 @@ public boolean isSpinnerFrequencyModulate() { public void reset() { layoutData.clear(); colorData.clear(); + hudSkinData = HUDSkinData.Default; + } + + @NonNull + public HUDSkinData getHUDSkinData() { + return hudSkinData; + } + + public void setHUDSkinData(@NonNull HUDSkinData hudSkinData) { + this.hudSkinData = hudSkinData; } } diff --git a/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java b/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java index fd6d9b808..fb4eb832b 100644 --- a/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java +++ b/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java @@ -3,7 +3,9 @@ import androidx.annotation.NonNull; import com.edlplan.framework.utils.interfaces.Consumer; +import com.reco1l.osu.hud.HUDSkinData; +import org.jetbrains.annotations.Nullable; import org.json.JSONArray; import org.json.JSONObject; @@ -64,6 +66,11 @@ protected void loadSkinBase() { currentFontsData = c; loadFonts(); }); + loadArray("HUD", currentData, (json) -> { + OsuSkin.get().hudSkinData = json == null + ? HUDSkinData.Default + : HUDSkinData.readFromJSON(json); + }); } @Override @@ -170,5 +177,9 @@ public void load(String tag, @NonNull JSONObject data, Consumer cons } consumer.consume(object); } + + public void loadArray(String tag, @NonNull JSONObject data, Consumer<@Nullable JSONArray> consumer) { + consumer.consume(data.optJSONArray(tag)); + } } From 3a91a0178db14d2c9ceb532ff332fc14e250ad6d Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sun, 16 Feb 2025 17:36:57 -0300 Subject: [PATCH 15/85] Adapt missing elements --- res/xml/settings_gameplay.xml | 43 ---- .../edlplan/ui/fragment/ModSettingsMenu.kt | 9 - src/com/reco1l/osu/hud/GameplayHUD.kt | 41 ++-- src/com/reco1l/osu/hud/HUDElement.kt | 19 +- src/com/reco1l/osu/hud/HUDSkinData.kt | 4 + src/com/reco1l/osu/hud/IGameplayEvents.kt | 16 ++ .../osu/hud/editor/HUDElementSelector.kt | 28 +-- .../hud/elements/HUDAverageOffsetCounter.kt | 25 +++ .../osu/hud/elements/HUDComboCounter.kt | 7 +- .../osu/hud/elements/HUDHitErrorMeter.kt | 111 ++++++++++ .../osu/hud/elements/HUDLinearSongProgress.kt | 68 +++++++ .../reco1l/osu/hud/elements/HUDPPCounter.kt | 2 +- .../osu/hud/elements/HUDPieSongProgress.kt | 11 - .../hud/elements/HUDUnstableRateCounter.kt | 23 +++ .../osu/ui/entity/GameplayLeaderboard.kt | 21 +- src/ru/nsu/ccfit/zuev/osu/Config.java | 68 +------ src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 192 ++++++------------ .../ccfit/zuev/osu/game/HitErrorMeter.java | 108 ---------- .../ccfit/zuev/osu/scoring/StatisticV2.java | 13 ++ 19 files changed, 391 insertions(+), 418 deletions(-) create mode 100644 src/com/reco1l/osu/hud/IGameplayEvents.kt create mode 100644 src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt create mode 100644 src/com/reco1l/osu/hud/elements/HUDHitErrorMeter.kt create mode 100644 src/com/reco1l/osu/hud/elements/HUDLinearSongProgress.kt create mode 100644 src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt delete mode 100644 src/ru/nsu/ccfit/zuev/osu/game/HitErrorMeter.java diff --git a/res/xml/settings_gameplay.xml b/res/xml/settings_gameplay.xml index cfa83610a..5f117800f 100644 --- a/res/xml/settings_gameplay.xml +++ b/res/xml/settings_gameplay.xml @@ -74,14 +74,6 @@ - - - - - - - - - - - - - diff --git a/src/com/edlplan/ui/fragment/ModSettingsMenu.kt b/src/com/edlplan/ui/fragment/ModSettingsMenu.kt index 787fb2ac1..bfd9bd117 100644 --- a/src/com/edlplan/ui/fragment/ModSettingsMenu.kt +++ b/src/com/edlplan/ui/fragment/ModSettingsMenu.kt @@ -139,15 +139,6 @@ class ModSettingsMenu : BaseFragment() { } } - findViewById(R.id.showScoreboard)!!.apply { - isChecked = Config.isShowScoreboard() - setOnCheckedChangeListener { _, isChecked -> - Config.setShowScoreboard(isChecked) - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putBoolean("showscoreboard", isChecked).commit() - } - } - findViewById(R.id.enableVideo)!!.apply { isChecked = Config.isVideoEnabled() setOnCheckedChangeListener { _, isChecked -> diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index b85019944..06186f40c 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -21,12 +21,10 @@ import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* import ru.nsu.ccfit.zuev.skins.OsuSkin import java.io.File +import kotlin.reflect.KClass import kotlin.reflect.full.primaryConstructor -class GameplayHUD( - private val statistics: StatisticV2, - private val gameScene: GameScene -) : Container() { +class GameplayHUD : Container(), IGameplayEvents { /** * The currently selected element. @@ -163,6 +161,7 @@ class GameplayHUD( comboCounter?.setPosition(10f, -10f) comboCounter?.setScale(1.28f) } + //endregion //region Elements events @@ -195,30 +194,34 @@ class GameplayHUD( } //endregion - //region Entity events - override fun onManagedUpdate(pSecondsElapsed: Float) { + //region Gameplay Events + + private fun forEachElement(action: (HUDElement) -> Unit) { mChildren?.fastForEach { - (it as? HUDElement)?.onGameplayUpdate(gameScene, statistics, pSecondsElapsed) + (it as? HUDElement)?.let(action) } - elementSelector?.onGameplayUpdate(gameScene, statistics, pSecondsElapsed) - super.onManagedUpdate(pSecondsElapsed) } - //endregion - //region Gameplay Events - fun onNoteHit(statistics: StatisticV2) { - mChildren?.fastForEach { - (it as? HUDElement)?.onNoteHit(statistics) - } + override fun onGameplayUpdate(gameScene: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + forEachElement { it.onGameplayUpdate(gameScene, statistics, secondsElapsed) } + elementSelector?.onGameplayUpdate(gameScene, statistics, secondsElapsed) + } + + override fun onNoteHit(statistics: StatisticV2) { + forEachElement { it.onNoteHit(statistics) } elementSelector?.onNoteHit(statistics) } - fun onBreakStateChange(isBreak: Boolean) { - mChildren?.fastForEach { - (it as? HUDElement)?.onBreakStateChange(isBreak) - } + override fun onBreakStateChange(isBreak: Boolean) { + forEachElement { it.onBreakStateChange(isBreak) } elementSelector?.onBreakStateChange(isBreak) } + + override fun onAccuracyRegister(accuracy: Float) { + forEachElement { it.onAccuracyRegister(accuracy) } + elementSelector?.onAccuracyRegister(accuracy) + } + //endregion diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 0964f312c..a0aab9d28 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -11,11 +11,16 @@ import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 import com.reco1l.osu.hud.editor.HUDElementToolbar import com.reco1l.osu.hud.elements.HUDAccuracyCounter +import com.reco1l.osu.hud.elements.HUDAverageOffsetCounter import com.reco1l.osu.hud.elements.HUDComboCounter import com.reco1l.osu.hud.elements.HUDHealthBar +import com.reco1l.osu.hud.elements.HUDHitErrorMeter +import com.reco1l.osu.hud.elements.HUDLinearSongProgress import com.reco1l.osu.hud.elements.HUDPPCounter import com.reco1l.osu.hud.elements.HUDPieSongProgress import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.osu.hud.elements.HUDUnstableRateCounter +import com.reco1l.osu.ui.entity.GameplayLeaderboard import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.game.GameScene @@ -33,10 +38,15 @@ val HUDElements = listOf( HUDPieSongProgress::class, HUDPPCounter::class, HUDScoreCounter::class, + HUDUnstableRateCounter::class, + HUDAverageOffsetCounter::class, + HUDHitErrorMeter::class, + HUDLinearSongProgress::class, + GameplayLeaderboard::class ) -abstract class HUDElement : Container() { +abstract class HUDElement : Container(), IGameplayEvents { /** * Returns the name of this element. @@ -200,12 +210,6 @@ abstract class HUDElement : Container() { //endregion - //region Gameplay Events - open fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) = Unit - open fun onNoteHit(statistics: StatisticV2) = Unit - open fun onBreakStateChange(isBreak: Boolean) = Unit - //endregion - override fun getParent(): GameplayHUD? { return super.getParent() as? GameplayHUD @@ -213,6 +217,7 @@ abstract class HUDElement : Container() { //region Edit mode + inner class HUDElementBackground : Container() { diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 24a542b12..3301abba8 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -13,6 +13,10 @@ import kotlin.reflect.KClass data class HUDSkinData(val elements: List) { + + fun hasElement(type: Class) = elements.any { it.type == type } + + companion object { /** diff --git a/src/com/reco1l/osu/hud/IGameplayEvents.kt b/src/com/reco1l/osu/hud/IGameplayEvents.kt new file mode 100644 index 000000000..9032b26cf --- /dev/null +++ b/src/com/reco1l/osu/hud/IGameplayEvents.kt @@ -0,0 +1,16 @@ +package com.reco1l.osu.hud + +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 + +interface IGameplayEvents { + + fun onGameplayUpdate(gameScene: GameScene, statistics: StatisticV2, secondsElapsed: Float) {} + + fun onNoteHit(statistics: StatisticV2) {} + + fun onBreakStateChange(isBreak: Boolean) {} + + fun onAccuracyRegister(accuracy: Float) {} + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index 813557306..be09908ce 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -12,8 +12,8 @@ import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec4 import com.reco1l.osu.hud.GameplayHUD -import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.hud.HUDElements +import com.reco1l.osu.hud.IGameplayEvents import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config @@ -22,7 +22,7 @@ import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 import kotlin.reflect.full.primaryConstructor -class HUDElementSelector(private val hud: GameplayHUD) : Container() { +class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayEvents { private val elements = HUDElements.map { it.primaryConstructor!!.call() } @@ -113,23 +113,23 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container() { //region Gameplay Events - fun onNoteHit(statistics: StatisticV2) { - elements.fastForEach { - (it as? HUDElement)?.onNoteHit(statistics) - } + + override fun onNoteHit(statistics: StatisticV2) { + elements.fastForEach { it.onNoteHit(statistics) } + } + + override fun onBreakStateChange(isBreak: Boolean) { + elements.fastForEach { it.onBreakStateChange(isBreak) } } - fun onBreakStateChange(isBreak: Boolean) { - elements.fastForEach { - (it as? HUDElement)?.onBreakStateChange(isBreak) - } + override fun onGameplayUpdate(gameScene: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + elements.fastForEach { it.onGameplayUpdate(gameScene, statistics, secondsElapsed) } } - fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { - elements.fastForEach { - (it as? HUDElement)?.onGameplayUpdate(game, statistics, secondsElapsed) - } + override fun onAccuracyRegister(accuracy: Float) { + elements.fastForEach { it.onAccuracyRegister(accuracy) } } + //endregion diff --git a/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt new file mode 100644 index 000000000..50bddd694 --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt @@ -0,0 +1,25 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.osu.hud.HUDElement +import ru.nsu.ccfit.zuev.osu.ResourceManager +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 +import kotlin.math.roundToInt + +class HUDAverageOffsetCounter : HUDElement() { + + private val text = ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + text = "Avg offset: 0ms" + } + + init { + attachChild(text) + } + + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + val avgOffset = if (game.offsetRegs > 0) game.offsetSum / game.offsetRegs else 0f + text.text = "Avg offset: ${(avgOffset * 1000).roundToInt()}ms" + } +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt index a3de6c4cb..2cfbe2256 100644 --- a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt @@ -88,9 +88,10 @@ class HUDComboCounter : HUDElement() { override fun onManagedUpdate(pSecondsElapsed: Float) { - popOutCount?.origin = anchor - displayedCountTextSprite.origin = anchor - + popOutCount?.scaleCenterX = anchor.x + popOutCount?.scaleCenterY = anchor.y + displayedCountTextSprite.scaleCenterX = anchor.x + displayedCountTextSprite.scaleCenterY = anchor.y super.onManagedUpdate(pSecondsElapsed) } diff --git a/src/com/reco1l/osu/hud/elements/HUDHitErrorMeter.kt b/src/com/reco1l/osu/hud/elements/HUDHitErrorMeter.kt new file mode 100644 index 000000000..2a84a48bd --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDHitErrorMeter.kt @@ -0,0 +1,111 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.andengine.Anchor +import com.reco1l.andengine.shape.Box +import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.framework.Pool +import com.reco1l.osu.hud.HUDElement +import com.reco1l.osu.updateThread +import ru.nsu.ccfit.zuev.osu.GlobalManager +import kotlin.math.abs + +class HUDHitErrorMeter : HUDElement() { + + + private val expiredIndicators = Pool(20) { Indicator() } + + private val hitWindow = GlobalManager.getInstance().gameScene.hitWindow + + + init { + setSize(WIDTH, INDICATOR_HEIGHT) + + // 50 + attachChild(RoundedBox().apply { + anchor = Anchor.Center + origin = Anchor.Center + cornerRadius = BAR_HEIGHT / 2 + setSize(WIDTH, BAR_HEIGHT) + setColor(200f / 255f, 180f / 255f, 110f / 255f) + }) + + // 100 + attachChild(Box().apply { + anchor = Anchor.Center + origin = Anchor.Center + setSize(WIDTH * (hitWindow.okWindow / hitWindow.mehWindow), BAR_HEIGHT) + setColor(100f / 255f, 220f / 255f, 40f / 255f) + }) + + // 300 + attachChild(Box().apply { + anchor = Anchor.Center + origin = Anchor.Center + setSize(WIDTH * (hitWindow.greatWindow / hitWindow.mehWindow), BAR_HEIGHT) + setColor(70f / 255f, 180f / 255f, 220f / 255f) + }) + + // Indicator + attachChild(RoundedBox().apply { + anchor = Anchor.Center + origin = Anchor.Center + cornerRadius = INDICATOR_WIDTH / 2 + setSize(INDICATOR_WIDTH, INDICATOR_HEIGHT) + }) + } + + + override fun onAccuracyRegister(accuracy: Float) { + + if (abs(accuracy * 1000) > hitWindow.mehWindow) { + return + } + + val indicator = expiredIndicators.obtain() + + indicator.x = (WIDTH / 2f) * (accuracy * 1000 / hitWindow.mehWindow) + indicator.alpha = 0.6f + + if (indicator.parent == null) { + attachChild(indicator) + } + } + + + inner class Indicator : RoundedBox() { + + init { + anchor = Anchor.Center + origin = Anchor.Center + cornerRadius = INDICATOR_WIDTH / 2 + setSize(INDICATOR_WIDTH, INDICATOR_HEIGHT - 2f) + } + + override fun onManagedUpdate(pSecondsElapsed: Float) { + + if (alpha > 0f) { + alpha -= 0.002f + } + + if (alpha <= 0f) { + alpha = 0f + + if (expiredIndicators.size == 20) { + updateThread { detachSelf() } + } else { + expiredIndicators.free(this) + } + } + } + } + + + companion object { + + private const val WIDTH = 400f + private const val BAR_HEIGHT = 12f + private const val INDICATOR_HEIGHT = BAR_HEIGHT + 14f + private const val INDICATOR_WIDTH = 4f + + } +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDLinearSongProgress.kt b/src/com/reco1l/osu/hud/elements/HUDLinearSongProgress.kt new file mode 100644 index 000000000..bb1b7e6d0 --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDLinearSongProgress.kt @@ -0,0 +1,68 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.andengine.* +import com.reco1l.andengine.shape.* +import com.reco1l.framework.* +import com.reco1l.osu.hud.HUDElement +import ru.nsu.ccfit.zuev.osu.Config +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 + + +class HUDLinearSongProgress : HUDElement() { + + private val backgroundRect = Box().apply { + + anchor = Anchor.BottomLeft + origin = Anchor.BottomLeft + relativeSizeAxes = Axes.X + setSize(1f, BAR_HEIGHT) + + color = ColorARGB.Black + alpha = 0.3f + } + + private val progressRect = Box().apply { + + anchor = Anchor.BottomLeft + origin = Anchor.BottomLeft + setSize(0f, BAR_HEIGHT) + alpha = 0.4f + } + + + init { + // Adding a 20epx padding so the user can grab the progress bar more easily. + setSize(Config.getRES_WIDTH().toFloat(), BAR_HEIGHT + 20f) + + attachChild(backgroundRect) + attachChild(progressRect) + } + + + fun setProgress(progress: Float, isIntro: Boolean) { + + if (isIntro) { + progressRect.setColor(153f / 255f, 204f / 255f, 51f / 255f) + } else { + progressRect.setColor(1f, 1f, 150f / 255f) + } + + progressRect.width = drawWidth * progress + } + + + override fun onGameplayUpdate(gameScene: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + if (gameScene.elapsedTime < gameScene.firstObjectStartTime) { + setProgress((gameScene.elapsedTime - gameScene.initialElapsedTime) / (gameScene.firstObjectStartTime - gameScene.initialElapsedTime), true) + } else { + setProgress((gameScene.elapsedTime - gameScene.firstObjectStartTime) / (gameScene.lastObjectEndTime - gameScene.firstObjectStartTime), false) + } + } + + + companion object { + private const val BAR_HEIGHT = 7f + } + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt index 788645ca6..caa5c12c2 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt @@ -24,6 +24,6 @@ class HUDPPCounter : HUDElement() { } override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { - // TODO: Update PP + setValue(statistics.pp) } } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt index 792436dac..386dbecad 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPieSongProgress.kt @@ -1,12 +1,9 @@ package com.reco1l.osu.hud.elements -import androidx.annotation.IntDef import com.reco1l.andengine.* import com.reco1l.andengine.shape.* import com.reco1l.framework.* import com.reco1l.osu.hud.HUDElement -import com.reco1l.osu.hud.elements.ProgressIndicatorType.Companion.BAR -import com.reco1l.osu.hud.elements.ProgressIndicatorType.Companion.PIE import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 @@ -90,12 +87,4 @@ class HUDPieSongProgress : HUDElement() { } } -} - -@IntDef(PIE, BAR) -annotation class ProgressIndicatorType { - companion object { - const val PIE = 0 - const val BAR = 1 - } } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt b/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt new file mode 100644 index 000000000..d57ae6729 --- /dev/null +++ b/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt @@ -0,0 +1,23 @@ +package com.reco1l.osu.hud.elements + +import com.reco1l.andengine.text.ExtendedText +import com.reco1l.osu.hud.HUDElement +import ru.nsu.ccfit.zuev.osu.ResourceManager +import ru.nsu.ccfit.zuev.osu.game.GameScene +import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 + +class HUDUnstableRateCounter : HUDElement() { + + private val text = ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + text = "UR: 0.00" + } + + init { + attachChild(text) + } + + override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + text.text = "UR: %.2f".format(statistics.unstableRate) + } +} \ No newline at end of file diff --git a/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt b/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt index 469f50776..352853bc0 100644 --- a/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt +++ b/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt @@ -1,8 +1,8 @@ package com.reco1l.osu.ui.entity import android.opengl.GLES20 +import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.multiplayer.Multiplayer.isMultiplayer -import org.anddev.andengine.entity.Entity import org.anddev.andengine.entity.sprite.Sprite import org.anddev.andengine.entity.text.ChangeableText import ru.nsu.ccfit.zuev.osu.Config @@ -11,7 +11,10 @@ import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.menu.ScoreBoardItem import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 -class GameplayLeaderboard(var playerName: String, private val stats: StatisticV2) : Entity(0f, 0f) { +class GameplayLeaderboard : HUDElement() { + + + val stats: StatisticV2 = GlobalManager.getInstance().gameScene.stat var nextItems: List? = null @@ -32,12 +35,9 @@ class GameplayLeaderboard(var playerName: String, private val stats: StatisticV2 private val isGlobalLeaderboard get() = GlobalManager.getInstance().songMenu.isBoardOnline - init { - isChildrenIgnoreUpdate = true - } - - override fun onManagedUpdate(secondsElapsed: Float) { + override fun onManagedUpdate(pSecondsElapsed: Float) { + super.onManagedUpdate(pSecondsElapsed) if (!isMultiplayer) { val items = GlobalManager.getInstance().songMenu.board @@ -186,7 +186,7 @@ class GameplayLeaderboard(var playerName: String, private val stats: StatisticV2 fun appendNewItem() = ScoreBoardItem().apply { - userName = playerName + userName = stats.playerName // Setting the initial rank as the last rank, in local leaderboard it'll always be the last index because // it's based on the local database. In online the server database provides up to 50 scores, so we can't know @@ -215,7 +215,7 @@ class GameplayLeaderboard(var playerName: String, private val stats: StatisticV2 } // In multiplayer, we try to find the corresponding data according to the username. - isMultiplayer -> list.find { it.userName == playerName } + isMultiplayer -> list.find { it.userName == stats.playerName } // In solo we just append a new data. else -> { @@ -309,7 +309,7 @@ class GameplayLeaderboard(var playerName: String, private val stats: StatisticV2 a = 0.5f if (data.isAlive || !isMultiplayer) { - val isOwnScore = !isMultiplayer && isGlobalLeaderboard && data.userName == playerName + val isOwnScore = !isMultiplayer && isGlobalLeaderboard && data.userName == stats.playerName info.setColor(0.9f, 0.9f, 0.9f) rank.setColor(0.6f, 0.6f, 0.6f, 0.9f) @@ -344,7 +344,6 @@ class GameplayLeaderboard(var playerName: String, private val stats: StatisticV2 companion object { private const val SPRITE_HEIGHT = 83 - private const val VERTICAL_PADDING = SPRITE_HEIGHT.toFloat() } } \ No newline at end of file diff --git a/src/ru/nsu/ccfit/zuev/osu/Config.java b/src/ru/nsu/ccfit/zuev/osu/Config.java index 7683a753e..cc9125e56 100644 --- a/src/ru/nsu/ccfit/zuev/osu/Config.java +++ b/src/ru/nsu/ccfit/zuev/osu/Config.java @@ -18,7 +18,6 @@ import java.util.UUID; import com.reco1l.osu.multiplayer.Multiplayer; -import com.reco1l.osu.hud.elements.ProgressIndicatorType; import net.margaritov.preference.colorpicker.ColorPickerPreference; @@ -48,8 +47,6 @@ public class Config { useCustomSounds, corovans, showFPS, - showAverageOffset, - showUnstableRate, animateFollowCircle, animateComboText, snakingInSliders, @@ -57,8 +54,6 @@ public class Config { showCursor, shrinkPlayfieldDownwards, hideNaviBar, - showScoreboard, - enablePP, enableExtension, loadAvatar, stayOnline, @@ -76,7 +71,6 @@ public class Config { receiveAnnouncements, enableStoryboard, safeBeatmapBg, - displayRealTimePPCounter, useNightcoreOnMultiplayer, videoEnabled, deleteUnsupportedVideos, @@ -88,11 +82,9 @@ public class Config { private static int RES_WIDTH, RES_HEIGHT, - errorMeter, spinnerStyle, - metronomeSwitch, - progressIndicatorType; - + metronomeSwitch; + private static float soundVolume, bgmVolume, offset, @@ -126,20 +118,15 @@ public static void loadConfig(final Context context) { comboburst = prefs.getBoolean("comboburst", false); corovans = prefs.getBoolean("images", false); showFPS = prefs.getBoolean("fps", true); - showAverageOffset = prefs.getBoolean("averageOffset", true); - showUnstableRate = prefs.getBoolean("unstableRate", true); - errorMeter = Integer.parseInt(prefs.getString("errormeter", "0")); spinnerStyle = Integer.parseInt(prefs.getString("spinnerstyle", "1")); showFirstApproachCircle = prefs.getBoolean("showfirstapproachcircle", false); metronomeSwitch = Integer.parseInt(prefs.getString("metronomeswitch", "1")); - showScoreboard = prefs.getBoolean("showscoreboard", true); enableStoryboard = prefs.getBoolean("enableStoryboard", false); videoEnabled = prefs.getBoolean("enableVideo", false); keepBackgroundAspectRatio = prefs.getBoolean("keepBackgroundAspectRatio", false); noChangeDimInBreaks = prefs.getBoolean("noChangeDimInBreaks", false); dimHitObjects = prefs.getBoolean("dimHitObjects", true); forceMaxRefreshRate = prefs.getBoolean("forceMaxRefreshRate", false); - progressIndicatorType = Integer.parseInt(prefs.getString("progressIndicatorType", "0")); setSize(); setPlayfieldSize(prefs.getInt("playfieldSize", 100) / 100f); @@ -224,7 +211,6 @@ public static void loadConfig(final Context context) { playMusicPreview = prefs.getBoolean("musicpreview", true); showCursor = prefs.getBoolean("showcursor", false); hideNaviBar = prefs.getBoolean("hidenavibar", false); - enablePP = false;//prefs.getBoolean("enablePP",true); fixFrameOffset = prefs.getBoolean("fixFrameOffset", true); removeSliderLock = prefs.getBoolean("removeSliderLock", false); displayScoreStatistics = prefs.getBoolean("displayScoreStatistics", false); @@ -232,7 +218,6 @@ public static void loadConfig(final Context context) { hideInGameUI = prefs.getBoolean("hideInGameUI", false); receiveAnnouncements = prefs.getBoolean("receiveAnnouncements", true); safeBeatmapBg = prefs.getBoolean("safebeatmapbg", false); - displayRealTimePPCounter = prefs.getBoolean("displayRealTimePPCounter", false); // Multiplayer useNightcoreOnMultiplayer = prefs.getBoolean("player_nightcore", false); @@ -307,10 +292,6 @@ public static boolean isDisplayScoreStatistics() { return displayScoreStatistics; } - public static boolean isDisplayRealTimePPCounter() { - return displayRealTimePPCounter; - } - public static DifficultyAlgorithm getDifficultyAlgorithm() { return Config.getString("difficultyAlgorithm", "0").equals("1") ? DifficultyAlgorithm.standard @@ -333,30 +314,6 @@ public static void setShowFPS(final boolean showFPS) { Config.showFPS = showFPS; } - public static boolean isShowAverageOffset() { - return showAverageOffset; - } - - public static void setShowAverageOffset(final boolean showAverageOffset) { - Config.showAverageOffset = showAverageOffset; - } - - public static boolean isShowUnstableRate() { - return showUnstableRate; - } - - public static void setShowUnstableRate(final boolean showUnstableRate) { - Config.showUnstableRate = showUnstableRate; - } - - public static boolean isShowScoreboard() { - return showScoreboard; - } - - public static void setShowScoreboard(final boolean showScoreboard) { - Config.showScoreboard = showScoreboard; - } - public static boolean isCorovans() { return corovans; } @@ -619,14 +576,6 @@ public static void setHideNaviBar(boolean hideNaviBar) { Config.hideNaviBar = hideNaviBar; } - public static boolean isEnablePP() { - return enablePP; - } - - public static void setEnablePP(boolean enablePP) { - Config.enablePP = enablePP; - } - public static String getScorePath() { return scorePath; } @@ -647,14 +596,6 @@ public static RGBColor[] getComboColors() { return comboColors; } - public static int getErrorMeter() { - return errorMeter; - } - - public static void setErrorMeter(int errorMeter) { - Config.errorMeter = errorMeter; - } - public static int getSpinnerStyle() { return spinnerStyle; } @@ -821,11 +762,6 @@ public static boolean isForceMaxRefreshRate() { return forceMaxRefreshRate; } - @ProgressIndicatorType - public static int getProgressIndicatorType() { - return progressIndicatorType; - } - // Shared Preferences // It's preferred to use these methods to access shared preferences instead of adding new fields to this class. diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 96e683ee8..b3ef817da 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -32,8 +32,8 @@ import com.reco1l.andengine.ExtendedScene; import com.reco1l.osu.hitobjects.FollowPointConnection; import com.reco1l.osu.hud.GameplayHUD; -import com.reco1l.osu.hud.elements.ProgressIndicatorType; import com.reco1l.osu.hitobjects.SliderTickSprite; +import com.reco1l.osu.hud.elements.HUDPPCounter; import com.reco1l.osu.ui.BlockAreaFragment; import com.reco1l.osu.ui.entity.GameplayLeaderboard; import com.reco1l.osu.multiplayer.Multiplayer; @@ -146,7 +146,6 @@ public class GameScene implements IUpdateHandler, GameObjectListener, private LinkedList expiredObjects; private Queue breakPeriods = new LinkedList<>(); public GameplayLeaderboard scoreBoard; - private HitErrorMeter hitErrorMeter; private Metronome metronome; private float scale; private float objectTimePreempt; @@ -167,8 +166,8 @@ public class GameScene implements IUpdateHandler, GameObjectListener, private Replay replay; private boolean replaying; private String replayFilePath; - private float offsetSum; - private int offsetRegs; + public float offsetSum; + public int offsetRegs; private Rectangle dimRectangle = null; private ComboBurst comboBurst; private int failcount = 0; @@ -183,7 +182,7 @@ public class GameScene implements IUpdateHandler, GameObjectListener, private ProxySprite storyboardOverlayProxy; - private HitWindow hitWindow; + public HitWindow hitWindow; private Job loadingJob; private DifficultyCalculationParameters lastDifficultyCalculationParameters; @@ -191,8 +190,6 @@ public class GameScene implements IUpdateHandler, GameObjectListener, private TimedDifficultyAttributes[] standardTimedDifficultyAttributes; private final List counterTexts = new ArrayList<>(5); - private ChangeableText avgOffsetText; - private ChangeableText urText; private ChangeableText memText; // Game @@ -220,11 +217,6 @@ public class GameScene implements IUpdateHandler, GameObjectListener, */ private GameplayHUD hud; - /** - * The linear song progress bar. - */ - private LinearSongProgress linearSongProgress; - /** * Whether the HUD editor mode is enabled. */ @@ -386,7 +378,7 @@ private void setBackground() { } } - private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, final CoroutineScope scope) { + private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, final CoroutineScope scope, boolean isHudEditor) { if (!SecurityUtils.verifyFileIntegrity(GlobalManager.getInstance().getMainActivity())) { ToastLogger.showText(com.osudroid.resources.R.string.file_integrity_tampered, true); return false; @@ -596,7 +588,7 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina var sameParameters = lastDifficultyCalculationParameters != null && lastDifficultyCalculationParameters.equals(parameters); - if (Config.isDisplayRealTimePPCounter()) { + if (!isHUDEditorMode && OsuSkin.get().getHUDSkinData().hasElement(HUDPPCounter.class)) { // Calculate timed difficulty attributes switch (Config.getDifficultyAlgorithm()) { case droid -> { @@ -654,7 +646,6 @@ public void startGame(BeatmapInfo beatmapInfo, String replayFile) { } public void startGame(BeatmapInfo beatmapInfo, String replayFile, boolean isHUDEditor) { - isHUDEditorMode = isHUDEditor; scene = new ExtendedScene(); @@ -698,10 +689,10 @@ public void startGame(BeatmapInfo beatmapInfo, String replayFile, boolean isHUDE boolean succeeded = false; try { - succeeded = loadGame(beatmapInfo != null ? beatmapInfo : lastBeatmapInfo, rfile, scope); + succeeded = loadGame(beatmapInfo != null ? beatmapInfo : lastBeatmapInfo, rfile, scope, isHUDEditor); if (succeeded) { - prepareScene(isHUDEditor); + prepareScene(); } } finally { if (!succeeded) { @@ -735,7 +726,7 @@ public void cancelLoading() { } } - private void prepareScene(boolean isHudEditor) { + private void prepareScene() { scene.setOnSceneTouchListener(this); if (GlobalManager.getInstance().getCamera() instanceof SmoothCamera) { SmoothCamera camera = (SmoothCamera) (GlobalManager.getInstance().getCamera()); @@ -782,61 +773,6 @@ private void prepareScene(boolean isHudEditor) { GameHelper.setScoreV2(stat.getMod().contains(GameMod.MOD_SCOREV2)); GameHelper.setEasy(stat.getMod().contains(GameMod.MOD_EASY)); - // Set up counter texts - for (var text : counterTexts) { - text.detachSelf(); - } - counterTexts.clear(); - - hud = new GameplayHUD(stat, this); - hud.setSkinData(OsuSkin.get().getHUDSkinData()); - hud.setEditMode(isHudEditor); - - var counterTextFont = ResourceManager.getInstance().getFont("smallFont"); - - if (Config.isShowFPS()) { - var fpsCounter = new FPSCounter(counterTextFont); - - // Attach a dummy entity for computing FPS, as its frame rate is tied to the draw thread and not - // the update thread. - hud.attachChild(new Entity() { - private long previousDrawTime; - - @Override - protected void onManagedDraw(GL10 pGL, Camera pCamera) { - long currentDrawTime = SystemClock.uptimeMillis(); - - fpsCounter.updateFps((currentDrawTime - previousDrawTime) / 1000f); - - previousDrawTime = currentDrawTime; - } - }); - - counterTexts.add(fpsCounter); - } - - if (Config.isShowUnstableRate() && !GameHelper.isAuto()) { - urText = new ChangeableText(720, 480, counterTextFont, "00.00 UR "); - counterTexts.add(urText); - } - - if (Config.isShowAverageOffset() && !GameHelper.isAuto()) { - avgOffsetText = new ChangeableText(720, 440, counterTextFont, "Avg offset: 0ms "); - counterTexts.add(avgOffsetText); - } - - if (BuildConfig.DEBUG) { - memText = new ChangeableText(780, 520, counterTextFont, "0/0 MB "); - counterTexts.add(memText); - } - - updateCounterTexts(); - - // Attach the counter texts - for (var text : counterTexts) { - hud.attachChild(text); - } - for (int i = 0; i < CursorCount; i++) { cursors[i] = new Cursor(); cursors[i].mouseDown = false; @@ -908,21 +844,6 @@ protected void onManagedDraw(GL10 pGL, Camera pCamera) { } } - linearSongProgress = null; - - if (!Config.isHideInGameUI()) { - if (Config.getProgressIndicatorType() == ProgressIndicatorType.BAR) { - linearSongProgress = new LinearSongProgress(hud.getParent(), lastObjectEndTime, firstObjectStartTime, new PointF(0, Config.getRES_HEIGHT() - 7), Config.getRES_WIDTH(), 7); - linearSongProgress.setProgressRectColor(new RGBColor(153f / 255f, 204f / 255f, 51f / 255f)); - linearSongProgress.setProgressRectAlpha(0.4f); - linearSongProgress.setInitialPassedTime(initialElapsedTime); - } - } - - if (Config.getErrorMeter() == 1 || (Config.getErrorMeter() == 2 && replaying)) { - hitErrorMeter = new HitErrorMeter(hud.getParent(), new PointF(Config.getRES_WIDTH() / 2f, Config.getRES_HEIGHT() - 20), 12, hitWindow); - } - skipBtn = null; if (skipTime > 1) { skipBtn = new AnimatedSprite("play-skip", true, OsuSkin.get().getAnimationFramerate()); @@ -976,8 +897,18 @@ protected void onManagedDraw(GL10 pGL, Camera pCamera) { fgScene.attachChild(unrankedSprite); } - String playname = Config.getOnlineUsername(); + if (GameHelper.isFlashLight()){ + flashlightSprite = new FlashLightEntity(); + fgScene.attachChild(flashlightSprite, 0); + } + + // HUD should be to the last so we ensure everything is initialized and ready to be used by + // the HUD elements in their constructors. + hud = new GameplayHUD(); + hud.setSkinData(OsuSkin.get().getHUDSkinData()); + hud.setEditMode(isHUDEditorMode); + String playname = Config.getOnlineUsername(); ChangeableText replayText = null; if (!Config.isHideReplayMarquee()) { @@ -1000,24 +931,53 @@ protected void onManagedDraw(GL10 pGL, Camera pCamera) { //noinspection DataFlowIssue playname = Multiplayer.player.getTeam().toString(); } + stat.setPlayerName(playname); - if (Config.isShowScoreboard()) { - scoreBoard = new GameplayLeaderboard(playname, stat); - hud.attachChild(scoreBoard); + for (var text : counterTexts) { + text.detachSelf(); } + counterTexts.clear(); - if (GameHelper.isFlashLight()){ - flashlightSprite = new FlashLightEntity(); - fgScene.attachChild(flashlightSprite, 0); + var counterTextFont = ResourceManager.getInstance().getFont("smallFont"); + + if (Config.isShowFPS()) { + var fpsCounter = new FPSCounter(counterTextFont); + + // Attach a dummy entity for computing FPS, as its frame rate is tied to the draw thread and not + // the update thread. + hud.attachChild(new Entity() { + private long previousDrawTime; + + @Override + protected void onManagedDraw(GL10 pGL, Camera pCamera) { + long currentDrawTime = SystemClock.uptimeMillis(); + + fpsCounter.updateFps((currentDrawTime - previousDrawTime) / 1000f); + + previousDrawTime = currentDrawTime; + } + }); + + counterTexts.add(fpsCounter); } - // Returning here to avoid start the game instantly - if (Multiplayer.isMultiplayer) - { + if (BuildConfig.DEBUG) { + memText = new ChangeableText(780, 520, counterTextFont, "0/0 MB "); + counterTexts.add(memText); + } + + updateCounterTexts(); + + // Attach the counter texts + for (var text : counterTexts) { + hud.attachChild(text); + } + + if (!Multiplayer.isMultiplayer) { + start(); + } else { RoomAPI.INSTANCE.notifyBeatmapLoaded(); - return; } - start(); } // This is used by the multiplayer system, is called once all players in the room completes beatmap loading. @@ -1279,10 +1239,6 @@ public void onUpdate(final float pSecondsElapsed) { } } - if (hitErrorMeter != null) { - hitErrorMeter.update(dt); - } - if (comboBurst != null) { if (stat.getCombo() == 0) { comboBurst.breakCombo(); @@ -1614,11 +1570,9 @@ private void updateActiveObjects(float deltaTime) { private void updatePassiveObjects(float deltaTime) { - breakAnimator.update(deltaTime); + hud.onGameplayUpdate(this, stat, deltaTime); - if (linearSongProgress != null) { - linearSongProgress.update(deltaTime); - } + breakAnimator.update(deltaTime); if (countdownAnimator != null) { countdownAnimator.update(deltaTime); @@ -2614,9 +2568,6 @@ public int getCursorsCount() { public void registerAccuracy(final double acc) { - if (hitErrorMeter != null) { - hitErrorMeter.putErrorResult((float) acc); - } offsetSum += (float) acc; offsetRegs++; @@ -2625,6 +2576,8 @@ public void registerAccuracy(final double acc) { if (replaying) { scoringScene.getReplayStat().addHitOffset(acc); } + + hud.onAccuracyRegister((float) acc); } @@ -2766,18 +2719,6 @@ public boolean saveFailedReplay() { } private void updateCounterTexts() { - // We are not updating FPS text as it is handled by FPSCounter, as well - // as PP text as it is updated in updatePPCounter. - if (avgOffsetText != null) { - float avgOffset = offsetRegs > 0 ? offsetSum / offsetRegs : 0; - - avgOffsetText.setText("Avg offset: " + Math.round(avgOffset * 1000) + "ms"); - } - - if (urText != null) { - urText.setText(Math.round(stat != null ? stat.getUnstableRate() : 0) + " UR"); - } - if (memText != null) { var totalMemory = Runtime.getRuntime().totalMemory(); var usedMemory = totalMemory - Runtime.getRuntime().freeMemory(); @@ -2794,7 +2735,7 @@ private void updateCounterTexts() { } private void updatePPCounter(int objectId) { - if (Config.isHideInGameUI() || !Config.isDisplayRealTimePPCounter()) { + if (Config.isHideInGameUI() || !isHUDEditorMode && !OsuSkin.get().getHUDSkinData().hasElement(HUDPPCounter.class)) { return; } @@ -2803,8 +2744,7 @@ private void updatePPCounter(int objectId) { case standard -> getStandardPPAt(objectId); }; - // TODO: Do PP counter stuff here - //hud.setPPCounterValue(!Double.isNaN(pp) ? pp : 0); + stat.setPP(pp); } private double getDroidPPAt(int objectId) { diff --git a/src/ru/nsu/ccfit/zuev/osu/game/HitErrorMeter.java b/src/ru/nsu/ccfit/zuev/osu/game/HitErrorMeter.java deleted file mode 100644 index f0cb35bf2..000000000 --- a/src/ru/nsu/ccfit/zuev/osu/game/HitErrorMeter.java +++ /dev/null @@ -1,108 +0,0 @@ -package ru.nsu.ccfit.zuev.osu.game; - -import android.graphics.PointF; - -import com.rian.osu.beatmap.HitWindow; - -import org.anddev.andengine.entity.primitive.Rectangle; -import org.anddev.andengine.entity.scene.Scene; - -import java.util.LinkedList; -import java.util.List; - -/** - * Created by dgsrz on 15/10/18. - */ -public class HitErrorMeter extends GameObject { - - private final Scene bgScene; - private final PointF barAnchor; - private final float barHeight; - private final float boundary; - private final List onDisplayIndicators; - private final List recycledIndicators; - - public HitErrorMeter(Scene scene, PointF anchor, float height, HitWindow hitWindow) { - barAnchor = anchor; - barHeight = height; - bgScene = scene; - - onDisplayIndicators = new LinkedList<>(); - recycledIndicators = new LinkedList<>(); - - boundary = hitWindow.getMehWindow() / 1000; - - float totalLen = boundary * 1500; - Rectangle hitMeter = new Rectangle(anchor.x - totalLen / 2, anchor.y - height, totalLen, height * 2); - hitMeter.setColor(0f, 0f, 0f, 0.8f); - scene.attachChild(hitMeter); - - float hit50Len = totalLen; - Rectangle hit50 = new Rectangle(anchor.x - hit50Len / 2, anchor.y - height / 2, hit50Len, height); - hit50.setColor(200f / 255f, 180f / 255f, 110f / 255f, 0.8f); - scene.attachChild(hit50); - - float hit100Len = hitWindow.getOkWindow() * 1.5f; - Rectangle hit100 = new Rectangle(anchor.x - hit100Len / 2, anchor.y - height / 2, hit100Len, height); - hit100.setColor(100f / 255f, 220f / 255f, 40f / 255f, 0.8f); - scene.attachChild(hit100); - - float hit300Len = hitWindow.getGreatWindow() * 1.5f; - Rectangle hit300 = new Rectangle(anchor.x - hit300Len / 2, anchor.y - height / 2, hit300Len, height); - hit300.setColor(70f / 255f, 180f / 255f, 220f / 255f, 0.8f); - scene.attachChild(hit300); - - Rectangle hitIndicator = new Rectangle(anchor.x - 2, anchor.y - height, 4, height * 2); - hitIndicator.setColor(1f, 1f, 1f, 0.8f); - hitIndicator.setZIndex(15); - scene.attachChild(hitIndicator); - } - - @Override - public void update(float dt) { - while (!onDisplayIndicators.isEmpty()) { - if (onDisplayIndicators.get(0).getAlpha() <= 0) { - Rectangle removed = onDisplayIndicators.remove(0); - removed.setVisible(false); - removed.setIgnoreUpdate(true); - removed.detachSelf(); - recycledIndicators.add(removed); - } else { - break; - } - } - for (int i = 0, size = onDisplayIndicators.size(); i < size; i++) { - var result = onDisplayIndicators.get(i); - float currentAlpha = result.getAlpha() - 0.002f; - result.setAlpha(currentAlpha); - } - } - - public void putErrorResult(float errorResult) { - if (Math.abs(errorResult) > boundary) { - return; - } - errorResult = errorResult * 750; - if (recycledIndicators.isEmpty()) { - Rectangle indicator = new Rectangle(barAnchor.x - 2, barAnchor.y - barHeight, 4, barHeight * 2); - float posX = indicator.getX() + errorResult; - float posY = indicator.getY(); - indicator.setPosition(posX, posY); - indicator.setColor(70f / 255f, 180f / 255f, 220f / 255f, 0.6f); - indicator.setZIndex(10); - bgScene.attachChild(indicator); - onDisplayIndicators.add(indicator); - } else { - Rectangle indicator = recycledIndicators.remove(0); - float posX = barAnchor.x - 2 + errorResult; - float posY = indicator.getY(); - indicator.setPosition(posX, posY); - indicator.setColor(70f / 255f, 180f / 255f, 220f / 255f, 0.6f); - indicator.setZIndex(10); - indicator.setVisible(true); - indicator.setIgnoreUpdate(false); - bgScene.attachChild(indicator); - onDisplayIndicators.add(indicator); - } - } -} diff --git a/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java b/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java index ca5c6c6c6..ebdc16f14 100644 --- a/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java +++ b/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java @@ -90,6 +90,11 @@ public class StatisticV2 implements Serializable { */ private String beatmapMD5 = ""; + /** + * The currnt performance points. + */ + private double pp = 0f; + public StatisticV2() {} @@ -956,4 +961,12 @@ public void processLegacySC(BeatmapInfo track) { customCS = cs + 4; } + + public void setPP(double value) { + pp = value; + } + + public double getPP() { + return pp; + } } From cbf3303cbde32a86ab4c825900fcba25871677cc Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sun, 16 Feb 2025 18:49:34 -0300 Subject: [PATCH 16/85] Avoid loosing original skin data --- src/com/reco1l/osu/hud/GameplayHUD.kt | 5 ++- src/com/reco1l/osu/hud/HUDElement.kt | 17 +++++----- .../reco1l/osu/hud/elements/HUDHealthBar.kt | 17 ++++++++++ .../osu/ui/entity/GameplayLeaderboard.kt | 31 ++++++++----------- .../nsu/ccfit/zuev/skins/SkinJsonReader.java | 4 +++ 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 06186f40c..9c1e4511b 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -20,6 +20,7 @@ import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* import ru.nsu.ccfit.zuev.skins.OsuSkin +import ru.nsu.ccfit.zuev.skins.SkinJsonReader import java.io.File import kotlin.reflect.KClass import kotlin.reflect.full.primaryConstructor @@ -83,7 +84,9 @@ class GameplayHUD : Container(), IGameplayEvents { json = JSONObject(jsonFile.reader().readText()) } else { jsonFile.createNewFile() - json = JSONObject() + + // We use the current skin data as a base to avoid losing any other skin data. + json = SkinJsonReader.getReader().currentData } json.put("HUD", HUDSkinData.writeToJSON(data)) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index a0aab9d28..2fedd2e5a 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -2,6 +2,8 @@ package com.reco1l.osu.hud import com.reco1l.andengine.Anchor import com.reco1l.andengine.container.Container +import com.reco1l.andengine.getDrawHeight +import com.reco1l.andengine.getDrawWidth import com.reco1l.andengine.originOffsetX import com.reco1l.andengine.originOffsetY import com.reco1l.andengine.shape.Line @@ -132,7 +134,7 @@ abstract class HUDElement : Container(), IGameplayEvents { val parentLocalY = drawY + localY if (event.isActionDown) { - parent!!.selected = this + (parent as? GameplayHUD)?.selected = this initialX = parentLocalX initialY = parentLocalY @@ -167,8 +169,8 @@ abstract class HUDElement : Container(), IGameplayEvents { val anchors = floatArrayOf(0f, 0.5f, 1f) val nearestAnchor = Vec2( - anchors.minBy { abs(drawX - originOffsetX - parent!!.drawWidth * it) }, - anchors.minBy { abs(drawY - originOffsetY - parent!!.drawHeight * it) } + anchors.minBy { abs(drawX - originOffsetX - parent!!.getDrawWidth() * it) }, + anchors.minBy { abs(drawY - originOffsetY - parent!!.getDrawHeight() * it) } ) if (nearestAnchor.x != anchor.x) { @@ -185,8 +187,8 @@ abstract class HUDElement : Container(), IGameplayEvents { private fun updateConnectionLine() { val pointOnParent = Vec2( - parent!!.drawWidth * anchor.x, - parent!!.drawHeight * anchor.y + parent!!.getDrawWidth() * anchor.x, + parent!!.getDrawHeight() * anchor.y ) val pointOnChild = Vec2( @@ -211,11 +213,6 @@ abstract class HUDElement : Container(), IGameplayEvents { //endregion - override fun getParent(): GameplayHUD? { - return super.getParent() as? GameplayHUD - } - - //region Edit mode inner class HUDElementBackground : Container() { diff --git a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt index d385df684..690662e24 100644 --- a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt +++ b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt @@ -8,6 +8,7 @@ import com.reco1l.andengine.sprite.* import com.reco1l.andengine.texture.* import com.reco1l.framework.* import com.reco1l.osu.hud.HUDElement +import com.reco1l.osu.hud.editor.HUDElementPreview import org.anddev.andengine.opengl.texture.region.* import ru.nsu.ccfit.zuev.osu.* import ru.nsu.ccfit.zuev.osu.game.GameScene @@ -90,6 +91,22 @@ class HUDHealthBar : HUDElement() { fillClear.setPosition(fill.x + fill.drawWidth, fill.y) onMeasureContentSize() + + + } + + + override fun onAttached() { + + if (parent !is HUDElementPreview) { + // Some skins use the background texture as a playfield background/border with a fullscreen + // size texture, this will make the health bar take the entire screen in the HUD editor. We + // clamp the size to the fill size to avoid this. + width = width.coerceAtMost(fill.drawX + fill.width) + height = height.coerceAtMost(fill.drawY + fill.height) + } + + super.onAttached() } diff --git a/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt b/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt index 352853bc0..7a5e4cbc0 100644 --- a/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt +++ b/src/com/reco1l/osu/ui/entity/GameplayLeaderboard.kt @@ -5,7 +5,6 @@ import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.multiplayer.Multiplayer.isMultiplayer import org.anddev.andengine.entity.sprite.Sprite import org.anddev.andengine.entity.text.ChangeableText -import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.GlobalManager import ru.nsu.ccfit.zuev.osu.ResourceManager import ru.nsu.ccfit.zuev.osu.menu.ScoreBoardItem @@ -14,8 +13,6 @@ import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 class GameplayLeaderboard : HUDElement() { - val stats: StatisticV2 = GlobalManager.getInstance().gameScene.stat - var nextItems: List? = null @@ -25,15 +22,13 @@ class GameplayLeaderboard : HUDElement() { private var lastTimeDataChange = 0L - // This determines the max amount of sprites that can be shown according to the user screen height. - private val maxAllowed = (Config.getRES_HEIGHT() - VERTICAL_PADDING * 2).toInt() / SPRITE_HEIGHT - - private val replayId get() = GlobalManager.getInstance().scoring.replayID + private val stats: StatisticV2 = GlobalManager.getInstance().gameScene.stat - private val isReplaying get() = replayId != -1 + private val replayId = GlobalManager.getInstance().scoring.replayID - private val isGlobalLeaderboard get() = GlobalManager.getInstance().songMenu.isBoardOnline + private val isReplaying = replayId != -1 + private val isGlobalLeaderboard = GlobalManager.getInstance().songMenu.isBoardOnline override fun onManagedUpdate(pSecondsElapsed: Float) { @@ -128,23 +123,23 @@ class GameplayLeaderboard : HUDElement() { setChildIndex(player, playerPosition) } - val maxY = VERTICAL_PADDING + SPRITE_HEIGHT * (maxAllowed - 1) + val maxY = SPRITE_HEIGHT * (SCORE_COUNT - 1) - if (playerPosition < maxAllowed) { + if (playerPosition < SCORE_COUNT) { var i = 0 while (i < spriteCount) { val sprite = getChild(i) - sprite.setPosition(0f, if (i >= maxAllowed) maxY else VERTICAL_PADDING + SPRITE_HEIGHT * i) - sprite.isVisible = i < maxAllowed + sprite.setPosition(0f, if (i >= SCORE_COUNT) maxY else SPRITE_HEIGHT * i) + sprite.isVisible = i < SCORE_COUNT ++i } } else { // Computing the bound from player position towards the limit of sprites that can be shown. - val minBound: Int = playerPosition - maxAllowed + 1 + val minBound: Int = playerPosition - SCORE_COUNT + 1 var i = 0 while (i < spriteCount) { @@ -159,14 +154,14 @@ class GameplayLeaderboard : HUDElement() { sprite.setPosition(0f, when { // First always on top - i == 0 -> VERTICAL_PADDING + i == 0 -> 0f // Player always on bottom i == playerPosition -> maxY // Sprites outside the bounds will be placed at its respective limit, at this point this sprite // shouldn't be visible. - !isInBounds -> if (i < minBound) VERTICAL_PADDING else maxY + !isInBounds -> if (i < minBound) 0f else maxY // Placing sprites respectively from maxY accounting for first sprite else -> maxY - SPRITE_HEIGHT * (playerPosition - i) @@ -343,7 +338,7 @@ class GameplayLeaderboard : HUDElement() { companion object { - private const val SPRITE_HEIGHT = 83 - private const val VERTICAL_PADDING = SPRITE_HEIGHT.toFloat() + private const val SPRITE_HEIGHT = 83f + private const val SCORE_COUNT = 5 } } \ No newline at end of file diff --git a/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java b/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java index fb4eb832b..6a52bf8fa 100644 --- a/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java +++ b/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java @@ -181,5 +181,9 @@ public void load(String tag, @NonNull JSONObject data, Consumer cons public void loadArray(String tag, @NonNull JSONObject data, Consumer<@Nullable JSONArray> consumer) { consumer.consume(data.optJSONArray(tag)); } + + public JSONObject getCurrentData() { + return currentData; + } } From 31d7d80dee97f5756e5240ee5a7ebd0e11723385 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sun, 16 Feb 2025 19:22:22 -0300 Subject: [PATCH 17/85] Implement a dialog prior to exit editor mode --- src/com/reco1l/andengine/text/ExtendedText.kt | 4 +- src/com/reco1l/osu/hud/GameplayHUD.kt | 45 ++++++++++++++----- .../osu/hud/editor/HUDElementToolbar.kt | 1 + src/com/reco1l/osu/ui/SettingsFragment.kt | 1 + src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 12 ++--- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/com/reco1l/andengine/text/ExtendedText.kt b/src/com/reco1l/andengine/text/ExtendedText.kt index 7c42670cf..90b38855f 100644 --- a/src/com/reco1l/andengine/text/ExtendedText.kt +++ b/src/com/reco1l/andengine/text/ExtendedText.kt @@ -150,8 +150,8 @@ open class ExtendedText : ExtendedEntity() { override fun finalize() { - if (textureBuffer!!.isManaged) { - textureBuffer!!.unloadFromActiveBufferObjectManager() + if (textureBuffer?.isManaged == true) { + textureBuffer?.unloadFromActiveBufferObjectManager() } super.finalize() } diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 9c1e4511b..d10f336bf 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -4,11 +4,14 @@ import android.util.Log import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes import com.reco1l.andengine.container.Container +import com.reco1l.osu.async import com.reco1l.osu.hud.editor.HUDElementSelector import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDComboCounter import com.reco1l.osu.hud.elements.HUDPieSongProgress import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.osu.mainThread +import com.reco1l.osu.ui.MessageDialog import com.reco1l.osu.updateThread import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.hud.* @@ -17,6 +20,7 @@ import org.json.JSONObject import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.GlobalManager import ru.nsu.ccfit.zuev.osu.ResourceManager +import ru.nsu.ccfit.zuev.osu.ToastLogger import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.* import ru.nsu.ccfit.zuev.skins.OsuSkin @@ -70,6 +74,31 @@ class GameplayHUD : Container(), IGameplayEvents { element.setEditMode(inEditMode) } + fun onBackPress() { + MessageDialog() + .setTitle("HUD Editor") + .setMessage("Do you want to save the changes?") + .addButton("Save") { + it.dismiss() + updateThread { + setEditMode(false) + ToastLogger.showText("Saving changes...", true) + saveToSkinJSON() + ToastLogger.showText("Changes saved!", true) + } + } + .addButton("Discard & Restore") { + it.dismiss() + updateThread { + setEditMode(false) + setSkinData(OsuSkin.get().hudSkinData) + ToastLogger.showText("Changes discarded!", true) + } + } + .addButton("Cancel") { it.dismiss() } + .show() + } + //region Skinning @@ -110,9 +139,6 @@ class GameplayHUD : Container(), IGameplayEvents { * Sets the skin data of the HUD. */ fun setSkinData(layoutData: HUDSkinData) { - - Log.i("GameplayHUD", "Setting skin data: $layoutData<") - mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) // First pass: We attach everything so that elements can reference between them when @@ -170,6 +196,7 @@ class GameplayHUD : Container(), IGameplayEvents { //region Elements events fun setEditMode(value: Boolean) { isInEditMode = value + GlobalManager.getInstance().gameScene.isHUDEditorMode = value if (value) { ResourceManager.getInstance().loadHighQualityAsset("delete", "delete.png") @@ -183,17 +210,13 @@ class GameplayHUD : Container(), IGameplayEvents { parent!!.registerTouchArea(elementSelector) } else { - updateThread { - parent!!.detachChild(elementSelector) - parent!!.unregisterTouchArea(elementSelector) + parent!!.detachChild(elementSelector) + parent!!.unregisterTouchArea(elementSelector) - elementSelector = null - } + elementSelector = null } - updateThread { - mChildren?.filterIsInstance()?.forEach { it.setEditMode(value) } - } + mChildren?.filterIsInstance()?.forEach { it.setEditMode(value) } } //endregion diff --git a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt index d6bf13e08..061792504 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt @@ -34,6 +34,7 @@ class HUDElementToolbar(private val element: HUDElement) : LinearContainer() { element.rotation += 90f }) + onMeasureContentSize() } private fun createToolbarButton(texture: String, back: ColorARGB, action: () -> Unit) = object : Container() { diff --git a/src/com/reco1l/osu/ui/SettingsFragment.kt b/src/com/reco1l/osu/ui/SettingsFragment.kt index 01ecc263e..05fb5651d 100644 --- a/src/com/reco1l/osu/ui/SettingsFragment.kt +++ b/src/com/reco1l/osu/ui/SettingsFragment.kt @@ -325,6 +325,7 @@ class SettingsFragment : com.edlplan.ui.fragment.SettingsFragment() { } else { dismiss() ModMenu.getInstance().mod = EnumSet.of(GameMod.MOD_AUTO) + GlobalManager.getInstance().gameScene.setOldScene(GlobalManager.getInstance().mainScene.scene) GlobalManager.getInstance().gameScene.startGame(GlobalManager.getInstance().selectedBeatmap, null, true) } true diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index b3ef817da..0cad601e1 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -35,6 +35,7 @@ import com.reco1l.osu.hitobjects.SliderTickSprite; import com.reco1l.osu.hud.elements.HUDPPCounter; import com.reco1l.osu.ui.BlockAreaFragment; +import com.reco1l.osu.ui.MessageDialog; import com.reco1l.osu.ui.entity.GameplayLeaderboard; import com.reco1l.osu.multiplayer.Multiplayer; import com.reco1l.osu.multiplayer.RoomScene; @@ -220,7 +221,7 @@ public class GameScene implements IUpdateHandler, GameObjectListener, /** * Whether the HUD editor mode is enabled. */ - private boolean isHUDEditorMode = false; + public boolean isHUDEditorMode = false; // Timing @@ -2175,14 +2176,7 @@ public void pause() { } if (isHUDEditorMode) { - hud.setEditMode(false); - isHUDEditorMode = false; - ToastLogger.showText("Saving HUD layout...", true); - - Execution.async(() -> { - hud.saveToSkinJSON(); - ToastLogger.showText("HUD layout saved successfully.", true); - }); + hud.onBackPress(); return; } From 808f6ec2ec49e8560d30548952df29298140b621 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Sun, 16 Feb 2025 22:00:41 -0300 Subject: [PATCH 18/85] Some corrections in the toolbar --- src/com/reco1l/andengine/Anchor.kt | 3 ++ src/com/reco1l/andengine/ExtendedEntity.kt | 10 +++- src/com/reco1l/osu/hud/GameplayHUD.kt | 20 ++++++- src/com/reco1l/osu/hud/HUDElement.kt | 53 +++++++++++++------ .../osu/hud/editor/HUDElementPreview.kt | 37 +++++++------ .../osu/hud/editor/HUDElementToolbar.kt | 53 ++++++++++--------- 6 files changed, 117 insertions(+), 59 deletions(-) diff --git a/src/com/reco1l/andengine/Anchor.kt b/src/com/reco1l/andengine/Anchor.kt index 3ab225f72..bff72cda2 100644 --- a/src/com/reco1l/andengine/Anchor.kt +++ b/src/com/reco1l/andengine/Anchor.kt @@ -32,6 +32,9 @@ object Anchor { val BottomRight = Vec2(1f, 1f) + val All = listOf(TopLeft, TopCenter, TopRight, CenterLeft, Center, CenterRight, BottomLeft, BottomCenter, BottomRight) + + fun getName(anchor: Vec2): String { return when (anchor) { TopLeft -> "TopLeft" diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index 22a4d40f9..784a3cd15 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -639,6 +639,13 @@ abstract class ExtendedEntity( } + override fun setRotation(pRotation: Float) { + if (mRotation != pRotation) { + mRotation = pRotation + invalidateTransformations() + } + } + /** * Sets the size of the entity. * @@ -855,8 +862,7 @@ abstract class ExtendedEntity( ): Boolean { val boundEntity = currentBoundEntity - if (boundEntity != null) { - boundEntity as IEntity + if (boundEntity != null && (boundEntity as? IEntity)?.parent == this) { val transformedX = localX - boundEntity.getDrawX() val transformedY = localY - boundEntity.getDrawY() diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index d10f336bf..38b0448e8 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -16,6 +16,7 @@ import com.reco1l.osu.updateThread import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.hud.* import org.anddev.andengine.entity.IEntity +import org.anddev.andengine.input.touch.TouchEvent import org.json.JSONObject import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.GlobalManager @@ -26,7 +27,6 @@ import ru.nsu.ccfit.zuev.osu.scoring.* import ru.nsu.ccfit.zuev.skins.OsuSkin import ru.nsu.ccfit.zuev.skins.SkinJsonReader import java.io.File -import kotlin.reflect.KClass import kotlin.reflect.full.primaryConstructor class GameplayHUD : Container(), IGameplayEvents { @@ -75,14 +75,21 @@ class GameplayHUD : Container(), IGameplayEvents { } fun onBackPress() { + + fun restore() { + x = 0f + width = Config.getRES_WIDTH().toFloat() + } + MessageDialog() .setTitle("HUD Editor") .setMessage("Do you want to save the changes?") .addButton("Save") { it.dismiss() updateThread { - setEditMode(false) ToastLogger.showText("Saving changes...", true) + restore() + setEditMode(false) saveToSkinJSON() ToastLogger.showText("Changes saved!", true) } @@ -90,6 +97,7 @@ class GameplayHUD : Container(), IGameplayEvents { .addButton("Discard & Restore") { it.dismiss() updateThread { + restore() setEditMode(false) setSkinData(OsuSkin.get().hudSkinData) ToastLogger.showText("Changes discarded!", true) @@ -255,6 +263,14 @@ class GameplayHUD : Container(), IGameplayEvents { // Nullable because during initialization the parent is not set yet. return super.getParent() as? HUD } + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (!super.onAreaTouched(event, localX, localY)) { + selected = null + return false + } + return true + } } diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 2fedd2e5a..0c69b218e 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -1,6 +1,8 @@ package com.reco1l.osu.hud import com.reco1l.andengine.Anchor +import com.reco1l.andengine.anchorOffsetX +import com.reco1l.andengine.anchorOffsetY import com.reco1l.andengine.container.Container import com.reco1l.andengine.getDrawHeight import com.reco1l.andengine.getDrawWidth @@ -25,9 +27,8 @@ import com.reco1l.osu.hud.elements.HUDUnstableRateCounter import com.reco1l.osu.ui.entity.GameplayLeaderboard import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager -import ru.nsu.ccfit.zuev.osu.game.GameScene -import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 import kotlin.math.abs +import kotlin.math.roundToInt /** @@ -109,7 +110,7 @@ abstract class HUDElement : Container(), IGameplayEvents { open fun onSelectionStateChange(isSelected: Boolean) { - (background as HUDElementBackground).isSelected = isSelected + (background as? HUDElementBackground)?.isSelected = isSelected toolbar?.isVisible = isSelected if (isSelected) { @@ -145,13 +146,16 @@ abstract class HUDElement : Container(), IGameplayEvents { val deltaX = parentLocalX - initialX val deltaY = parentLocalY - initialY - setPosition(x + deltaX, y + deltaY) - findNearestAnchorPoint() - updateConnectionLine() + // Preventing from moving the element if it's not selected. + if ((parent as? GameplayHUD)?.selected == this) { - initialX = parentLocalX - initialY = parentLocalY - return true + setAbsolutePosition(deltaX, deltaY) + updateConnectionLine() + + initialX = parentLocalX + initialY = parentLocalY + return true + } } } @@ -164,14 +168,23 @@ abstract class HUDElement : Container(), IGameplayEvents { toolbar?.invalidateTransformations() } - private fun findNearestAnchorPoint() { - val anchors = floatArrayOf(0f, 0.5f, 1f) + private fun setAbsolutePosition(deltaX: Float, deltaY: Float) { - val nearestAnchor = Vec2( - anchors.minBy { abs(drawX - originOffsetX - parent!!.getDrawWidth() * it) }, - anchors.minBy { abs(drawY - originOffsetY - parent!!.getDrawHeight() * it) } - ) + val anchors = Anchor.All + + val parentWidth = parent!!.getDrawWidth() + val parentHeight = parent!!.getDrawHeight() + + val nearestAnchor = anchors.minBy { anchor -> + val x = parentWidth * anchor.x - drawX + deltaX + val y = parentHeight * anchor.y - drawY + deltaY + + return@minBy x * x + y * y + } + + x += deltaX + y += deltaY if (nearestAnchor.x != anchor.x) { x = -x @@ -182,6 +195,7 @@ abstract class HUDElement : Container(), IGameplayEvents { } anchor = nearestAnchor + origin = nearestAnchor } private fun updateConnectionLine() { @@ -212,9 +226,16 @@ abstract class HUDElement : Container(), IGameplayEvents { //endregion - //region Edit mode + /** + * Removes the element from the HUD. + */ + fun remove() { + setEditMode(false) + detachSelf() + } + inner class HUDElementBackground : Container() { diff --git a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt index 45e335292..51be6c80b 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt @@ -11,13 +11,24 @@ import com.reco1l.framework.math.Vec4 import com.reco1l.osu.hud.GameplayHUD import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.hud.HUDElementSkinData +import org.anddev.andengine.engine.camera.Camera import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager +import javax.microedition.khronos.opengles.GL10 import kotlin.math.abs +import kotlin.math.min -class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Container() { +class HUDElementPreview(private val element: HUDElement, val hud: GameplayHUD): Container() { + private val label = ExtendedText().apply { + font = ResourceManager.getInstance().getFont("smallFont") + anchor = Anchor.BottomLeft + origin = Anchor.BottomLeft + text = element.name + color = ColorARGB.White + } + init { width = HUDElementSelector.SELECTOR_WIDTH - 16f * 2 height = 120f @@ -30,26 +41,21 @@ class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Containe cornerRadius = 12f } - attachChild(ExtendedText().apply { - font = ResourceManager.getInstance().getFont("smallFont") - anchor = Anchor.BottomLeft - origin = Anchor.BottomLeft - text = element.name - color = ColorARGB.White - }) - attachChild(element) + attachChild(label) element.setSkinData(HUDElementSkinData(element::class)) + } - // Scaling the element inside the box + override fun onManagedDraw(gl: GL10, camera: Camera) { - if (element.drawHeight > getPaddedHeight()) { - element.setScale(getPaddedHeight() / element.drawHeight) + // Scaling the element inside the box + if (element.drawWidth > element.drawHeight) { + element.setScale(min(1f, getPaddedWidth() / element.drawWidth)) + } else { + element.setScale(min(1f, (getPaddedHeight() - label.drawHeight) / element.drawHeight)) } - if (element.drawWidth > getPaddedWidth()) { - element.setScale(getPaddedWidth() / element.drawWidth) - } + super.onManagedDraw(gl, camera) } //region Input handling @@ -71,6 +77,7 @@ class HUDElementPreview(val element: HUDElement, val hud: GameplayHUD): Containe } if (event.isActionMove) { + initialTime = System.currentTimeMillis() clearEntityModifiers() scaleTo(1f, 0.1f) } diff --git a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt index 061792504..5671b25e8 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt @@ -20,24 +20,45 @@ class HUDElementToolbar(private val element: HUDElement) : LinearContainer() { isVisible = false spacing = 4f - attachChild(createToolbarButton("delete", ColorARGB(0xFF260000)) { + attachChild(Button("delete", ColorARGB(0xFF260000)) { updateThread { - element.parent?.detachChild(element) + element.remove() } }) - attachChild(createToolbarButton("rotate_left", ColorARGB(0xFF181825)) { + attachChild(Button("rotate_left", ColorARGB(0xFF181825)) { element.rotation -= 90f }) - attachChild(createToolbarButton("rotate_right", ColorARGB(0xFF181825)) { + attachChild(Button("rotate_right", ColorARGB(0xFF181825)) { element.rotation += 90f }) onMeasureContentSize() } - private fun createToolbarButton(texture: String, back: ColorARGB, action: () -> Unit) = object : Container() { + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (!isVisible) { + return false + } + return super.onAreaTouched(event, localX, localY) + } + + override fun onManagedUpdate(pSecondsElapsed: Float) { + + x = element.drawX + element.drawWidth / 2 - drawWidth / 2 + + if (element.drawY < drawHeight) { + y = element.drawY + element.drawHeight + 4f + } else { + y = element.drawY - drawHeight - 4f + } + + super.onManagedUpdate(pSecondsElapsed) + } + + + inner class Button(texture: String, back: ColorARGB, val action: () -> Unit) : Container() { init { setSize(46f, 46f) @@ -52,7 +73,7 @@ class HUDElementToolbar(private val element: HUDElement) : LinearContainer() { anchor = Anchor.Center origin = Anchor.Center relativeSizeAxes = Axes.Both - setSize(0.9f, 0.9f) + setSize(0.8f, 0.8f) }) } @@ -62,29 +83,13 @@ class HUDElementToolbar(private val element: HUDElement) : LinearContainer() { action() return true } - return false - } - - override fun onManagedUpdate(pSecondsElapsed: Float) { - x = element.drawX + element.drawWidth / 2 - drawWidth / 2 - - if (element.drawY < drawHeight) { - y = element.drawY + element.drawHeight + 4f - } else { - y = element.drawY - drawHeight - 4f + if (event.isActionDown) { + return true } - super.onManagedUpdate(pSecondsElapsed) - } - } - - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (!isVisible) { return false } - return super.onAreaTouched(event, localX, localY) } } \ No newline at end of file From d3e365830d409fe52c17d9c9592081d7a102d634 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 17 Feb 2025 14:51:07 -0300 Subject: [PATCH 19/85] Fix auto-anchor system --- src/com/reco1l/andengine/Anchor.kt | 13 +++++-- src/com/reco1l/andengine/Entities.kt | 42 ++++++++++++++++++++++ src/com/reco1l/framework/math/Vec2.kt | 47 ++++++------------------ src/com/reco1l/osu/hud/HUDElement.kt | 52 +++++++++++++-------------- 4 files changed, 90 insertions(+), 64 deletions(-) diff --git a/src/com/reco1l/andengine/Anchor.kt b/src/com/reco1l/andengine/Anchor.kt index bff72cda2..9893c011f 100644 --- a/src/com/reco1l/andengine/Anchor.kt +++ b/src/com/reco1l/andengine/Anchor.kt @@ -32,8 +32,17 @@ object Anchor { val BottomRight = Vec2(1f, 1f) - val All = listOf(TopLeft, TopCenter, TopRight, CenterLeft, Center, CenterRight, BottomLeft, BottomCenter, BottomRight) - + fun getAll() = listOf( + TopLeft, + TopCenter, + TopRight, + CenterLeft, + Center, + CenterRight, + BottomLeft, + BottomCenter, + BottomRight + ) fun getName(anchor: Vec2): String { return when (anchor) { diff --git a/src/com/reco1l/andengine/Entities.kt b/src/com/reco1l/andengine/Entities.kt index 00a3f653e..4f624817d 100644 --- a/src/com/reco1l/andengine/Entities.kt +++ b/src/com/reco1l/andengine/Entities.kt @@ -40,6 +40,34 @@ var ExtendedEntity.size height = value.y } +/** + * The draw size of the entity. + */ +val ExtendedEntity.drawSize + get() = Vec2(drawWidth, drawHeight) + +/** + * The position of the entity. + */ +var ExtendedEntity.position + get() = Vec2(x, y) + set(value) { + x = value.x + y = value.y + } + +/** + * The draw position of the entity. + */ +val ExtendedEntity.drawPosition + get() = Vec2(drawX, drawY) + + +/** + * The total offset applied to the entity. + */ +val ExtendedEntity.totalOffset + get() = Vec2(totalOffsetX, totalOffsetY) /** * The total offset applied to the X axis. @@ -53,6 +81,13 @@ val ExtendedEntity.totalOffsetX val ExtendedEntity.totalOffsetY get() = originOffsetY + anchorOffsetY + translationY + +/** + * The offset applied to the entity according to the anchor factor. + */ +val ExtendedEntity.anchorOffset + get() = Vec2(anchorOffsetX, anchorOffsetY) + /** * The offset applied to the X axis according to the anchor factor. */ @@ -65,6 +100,13 @@ val ExtendedEntity.anchorOffsetX: Float val ExtendedEntity.anchorOffsetY: Float get() = parent.getPaddedHeight() * anchor.y + +/** + * The offset applied to the entity according to the origin factor. + */ +val ExtendedEntity.originOffset + get() = Vec2(originOffsetX, originOffsetY) + /** * The offset applied to the X axis according to the origin factor. */ diff --git a/src/com/reco1l/framework/math/Vec2.kt b/src/com/reco1l/framework/math/Vec2.kt index d375d571b..485bf1cad 100644 --- a/src/com/reco1l/framework/math/Vec2.kt +++ b/src/com/reco1l/framework/math/Vec2.kt @@ -1,16 +1,11 @@ package com.reco1l.framework.math -data class Vec2( +import kotlin.math.hypot - val x: Float, - - val y: Float, - -) { +data class Vec2(val x: Float, val y: Float) { constructor(value: Float = 0f) : this(value, value) - val total get() = x + y @@ -21,44 +16,24 @@ data class Vec2( get() = x - operator fun plus(other: Vec2) = Vec2( - x + other.x, - y + other.y - ) - - operator fun minus(other: Vec2) = Vec2( - x - other.x, - y - other.y - ) + operator fun plus(other: Vec2) = Vec2(x + other.x, y + other.y) + operator fun minus(other: Vec2) = Vec2(x - other.x, y - other.y) + operator fun times(other: Vec2) = Vec2(x * other.x, y * other.y) + operator fun div(other: Vec2) = Vec2(x / other.x, y / other.y) - operator fun times(scalar: Float) = Vec2( - x * scalar, - y * scalar, - ) + operator fun times(scalar: Float) = Vec2(x * scalar, y * scalar) + operator fun div(scalar: Float) = Vec2(x / scalar, y / scalar) - operator fun div(scalar: Float) = Vec2( - x / scalar, - y / scalar - ) + operator fun unaryMinus() = Vec2(-x, -y) - operator fun unaryMinus() = Vec2( - -x, - -y - ) - - override fun toString() = "Vector2($x, $y)" + fun distance(other: Vec2) = hypot(x - other.x, y - other.y) override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Vec2) return false - - return x == other.x && y == other.y + return this === other || other is Vec2 && x == other.x && y == other.y } - companion object { val Zero = Vec2() val One = Vec2(1f, 1f) } - } \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 0c69b218e..468aeaec9 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -1,13 +1,15 @@ package com.reco1l.osu.hud import com.reco1l.andengine.Anchor -import com.reco1l.andengine.anchorOffsetX -import com.reco1l.andengine.anchorOffsetY +import com.reco1l.andengine.ExtendedEntity +import com.reco1l.andengine.anchorOffset import com.reco1l.andengine.container.Container +import com.reco1l.andengine.drawPosition +import com.reco1l.andengine.drawSize import com.reco1l.andengine.getDrawHeight import com.reco1l.andengine.getDrawWidth -import com.reco1l.andengine.originOffsetX -import com.reco1l.andengine.originOffsetY +import com.reco1l.andengine.originOffset +import com.reco1l.andengine.position import com.reco1l.andengine.shape.Line import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText @@ -28,7 +30,7 @@ import com.reco1l.osu.ui.entity.GameplayLeaderboard import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager import kotlin.math.abs -import kotlin.math.roundToInt +import kotlin.math.min /** @@ -148,8 +150,9 @@ abstract class HUDElement : Container(), IGameplayEvents { // Preventing from moving the element if it's not selected. if ((parent as? GameplayHUD)?.selected == this) { - - setAbsolutePosition(deltaX, deltaY) + applyClosestAnchorOrigin() + x += deltaX + y += deltaY updateConnectionLine() initialX = parentLocalX @@ -169,33 +172,30 @@ abstract class HUDElement : Container(), IGameplayEvents { } - private fun setAbsolutePosition(deltaX: Float, deltaY: Float) { - - val anchors = Anchor.All + private fun applyClosestAnchorOrigin() { - val parentWidth = parent!!.getDrawWidth() - val parentHeight = parent!!.getDrawHeight() + val drawSize = drawSize + val drawPosition = drawPosition + val parentDrawSize = (parent as ExtendedEntity).drawSize - val nearestAnchor = anchors.minBy { anchor -> - val x = parentWidth * anchor.x - drawX + deltaX - val y = parentHeight * anchor.y - drawY + deltaY + val relativeTopLeftDrawPosition = drawPosition / parentDrawSize + val relativeBottomRightDrawPosition = (drawPosition + drawSize) / parentDrawSize - return@minBy x * x + y * y + val closest = Anchor.getAll().minBy { + min(abs(relativeTopLeftDrawPosition.distance(it)), abs(relativeBottomRightDrawPosition.distance(it))) } - x += deltaX - y += deltaY - - if (nearestAnchor.x != anchor.x) { - x = -x + if (anchor != closest) { + val previousAnchorOffset = anchorOffset + anchor = closest + position -= anchorOffset - previousAnchorOffset } - if (nearestAnchor.y != anchor.y) { - y = -y + if (origin != closest) { + val previousOriginOffset = originOffset + origin = closest + position -= originOffset - previousOriginOffset } - - anchor = nearestAnchor - origin = nearestAnchor } private fun updateConnectionLine() { From 4a9f9871ddbac78ffef0dbbbd8270b48c25eadd0 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 17 Feb 2025 15:43:21 -0300 Subject: [PATCH 20/85] Some corrections to input binding system --- src/com/reco1l/andengine/ExtendedEntity.kt | 49 ++++++--------- .../container/ScrollableContainer.kt | 62 +++++++++---------- src/com/reco1l/osu/hud/GameplayHUD.kt | 6 +- src/com/reco1l/osu/hud/HUDElement.kt | 21 +++---- .../osu/hud/editor/HUDElementSelector.kt | 6 +- 5 files changed, 67 insertions(+), 77 deletions(-) diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index 784a3cd15..bebfa36a5 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -9,7 +9,6 @@ import com.reco1l.toolkt.kotlin.fastForEach import org.anddev.andengine.engine.camera.* import org.anddev.andengine.entity.* import org.anddev.andengine.entity.scene.Scene -import org.anddev.andengine.entity.scene.Scene.ITouchArea import org.anddev.andengine.entity.shape.* import org.anddev.andengine.input.touch.TouchEvent import org.anddev.andengine.opengl.util.* @@ -312,8 +311,6 @@ abstract class ExtendedEntity( private var isVertexBufferDirty = true - private var currentBoundEntity: ITouchArea? = null - // Attachment @@ -843,47 +840,38 @@ abstract class ExtendedEntity( } - // Input + //region Input + + private val inputBindings = arrayOfNulls(10) + - open fun invalidateInputBinding(recursively: Boolean = true) { - currentBoundEntity = null + open fun invalidateInputBindings(recursively: Boolean = true) { + inputBindings.fill(null) if (recursively) { mChildren?.fastForEach { - (it as? ExtendedEntity)?.invalidateInputBinding() + (it as? ExtendedEntity)?.invalidateInputBindings() } } } - override fun onAreaTouched( - event: TouchEvent, - localX: Float, - localY: Float - ): Boolean { + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - val boundEntity = currentBoundEntity - if (boundEntity != null && (boundEntity as? IEntity)?.parent == this) { + val inputBinding = inputBindings.getOrNull(event.pointerID) - val transformedX = localX - boundEntity.getDrawX() - val transformedY = localY - boundEntity.getDrawY() - - if (!boundEntity.onAreaTouched(event, transformedX, transformedY)) { - currentBoundEntity = null + if (inputBinding != null && inputBinding.parent == this) { + if (!inputBinding.onAreaTouched(event, localX - inputBinding.drawX, localY - inputBinding.drawY) || event.isActionUp) { + inputBindings[event.pointerID] = null + return false } return true } try { - for (i in childCount - 1 downTo 0) { - val child = getChild(i) - - if (child is ITouchArea && child.contains(localX, localY)) { - - val transformedX = localX - child.getDrawX() - val transformedY = localY - child.getDrawY() - - if (child.onAreaTouched(event, transformedX, transformedY)) { - currentBoundEntity = child + mChildren?.fastForEach { child -> + if (child is ExtendedEntity && child.contains(localX, localY)) { + if (child.onAreaTouched(event, localX - child.drawX, localY - child.drawY)) { + inputBindings[event.pointerID] = child return true } } @@ -891,7 +879,10 @@ abstract class ExtendedEntity( } catch (e: IndexOutOfBoundsException) { Log.e("ExtendedEntity", "A child entity was removed during touch event propagation.", e) } + return false } + //endregion + } diff --git a/src/com/reco1l/andengine/container/ScrollableContainer.kt b/src/com/reco1l/andengine/container/ScrollableContainer.kt index 094d5a0a5..ed93e472a 100644 --- a/src/com/reco1l/andengine/container/ScrollableContainer.kt +++ b/src/com/reco1l/andengine/container/ScrollableContainer.kt @@ -37,6 +37,7 @@ open class ScrollableContainer : Container() { indicatorX?.alpha = 0.5f field = value invalidateTransformations() + invalidateInputBindings() } /** @@ -57,6 +58,7 @@ open class ScrollableContainer : Container() { indicatorY?.alpha = 0.5f field = value invalidateTransformations() + invalidateInputBindings() } /** @@ -282,11 +284,6 @@ open class ScrollableContainer : Container() { override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (super.onAreaTouched(event, localX, localY)) { - return false - } - invalidateInputBinding() - when (event.action) { ACTION_DOWN -> { @@ -295,7 +292,6 @@ open class ScrollableContainer : Container() { velocityX = 0f velocityY = 0f - return true } ACTION_MOVE -> { @@ -305,46 +301,46 @@ open class ScrollableContainer : Container() { isDragging = abs(deltaX) > 1f || abs(deltaY) > 1f - if (!isDragging) { - return super.onAreaTouched(event, localX, localY) - } + if (isDragging) { - val length = hypot(deltaX, deltaY) + val length = hypot(deltaX, deltaY) - if (scrollX - deltaX < 0 || scrollX - deltaX > maxScrollX) { - deltaX *= if (length <= 0) 0f else length.pow(0.7f) / length - } + if (scrollX - deltaX < 0 || scrollX - deltaX > maxScrollX) { + deltaX *= if (length <= 0) 0f else length.pow(0.7f) / length + } - if (scrollY - deltaY < 0 || scrollY - deltaY > maxScrollY) { - deltaY *= if (length <= 0) 0f else length.pow(0.7f) / length - } + if (scrollY - deltaY < 0 || scrollY - deltaY > maxScrollY) { + deltaY *= if (length <= 0) 0f else length.pow(0.7f) / length + } - val dragTimeSec = elapsedTimeSec - lastDragTimeSec + val dragTimeSec = elapsedTimeSec - lastDragTimeSec - if (abs(deltaX) > 0.1f) { - scrollX -= deltaX - velocityX = abs(deltaX / dragTimeSec) * sign(deltaX) - initialX = localX - } + if (abs(deltaX) > 0.1f) { + scrollX -= deltaX + velocityX = abs(deltaX / dragTimeSec) * sign(deltaX) + initialX = localX + } - if (abs(deltaY) > 0.1f) { - scrollY -= deltaY - velocityY = abs(deltaY / dragTimeSec) * sign(deltaY) - initialY = localY - } + if (abs(deltaY) > 0.1f) { + scrollY -= deltaY + velocityY = abs(deltaY / dragTimeSec) * sign(deltaY) + initialY = localY + } - lastDragTimeSec = elapsedTimeSec - return true + lastDragTimeSec = elapsedTimeSec + } } else -> { - if (!isDragging) { - return super.onAreaTouched(event, localX, localY) - } isDragging = false - return false } } + + if (!isDragging) { + super.onAreaTouched(event, localX, localY) + } + + return true } diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 38b0448e8..dfbaf7ee2 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -265,11 +265,11 @@ class GameplayHUD : Container(), IGameplayEvents { } override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (!super.onAreaTouched(event, localX, localY)) { + val result = super.onAreaTouched(event, localX, localY) + if (event.isActionDown && !result) { selected = null - return false } - return true + return result } } diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 468aeaec9..556d90676 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -15,7 +15,7 @@ import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.editor.HUDElementToolbar +import com.reco1l.osu.hud.editor.HUDElementOverlay import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDAverageOffsetCounter import com.reco1l.osu.hud.elements.HUDComboCounter @@ -60,7 +60,7 @@ abstract class HUDElement : Container(), IGameplayEvents { get() = this::class.simpleName!!.replace("HUD", "").replace("([a-z])([A-Z])".toRegex(), "$1 $2") - private var toolbar: HUDElementToolbar? = null + private var overlay: HUDElementOverlay? = null private var connectionLine: Line? = null @@ -97,23 +97,23 @@ abstract class HUDElement : Container(), IGameplayEvents { if (value) { background = HUDElementBackground() - toolbar = HUDElementToolbar(this) + overlay = HUDElementOverlay(this) - parent!!.attachChild(toolbar!!) + parent!!.attachChild(overlay!!) } else { connectionLine?.detachSelf() - toolbar?.detachSelf() + overlay?.detachSelf() connectionLine = null background = null - toolbar = null + overlay = null } } open fun onSelectionStateChange(isSelected: Boolean) { (background as? HUDElementBackground)?.isSelected = isSelected - toolbar?.isVisible = isSelected + overlay?.isVisible = isSelected if (isSelected) { updateConnectionLine() @@ -160,15 +160,14 @@ abstract class HUDElement : Container(), IGameplayEvents { return true } } - } + return false } override fun invalidateTransformations() { super.invalidateTransformations() - - toolbar?.invalidateTransformations() + overlay?.invalidateTransformations() } @@ -236,7 +235,7 @@ abstract class HUDElement : Container(), IGameplayEvents { detachSelf() } - inner class HUDElementBackground : Container() { + private inner class HUDElementBackground : Container() { var isSelected = false diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index be09908ce..441b80835 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -62,6 +62,10 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (event.isActionDown) { + return true + } + if (event.isActionUp) { this@HUDElementSelector.clearEntityModifiers() @@ -77,8 +81,8 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE hud.moveToX(0f, 0.2f) hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) } + return false } - return false } From b14e3bf856d77049c53ac95ced6b369179450062 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 17 Feb 2025 21:51:26 -0300 Subject: [PATCH 21/85] Implement scale tip in the HUD element overlay --- assets/rotate.png | Bin 0 -> 1774 bytes assets/{expand.png => scale.png} | Bin src/com/reco1l/framework/math/Vec2.kt | 3 + src/com/reco1l/osu/hud/GameplayHUD.kt | 16 +- src/com/reco1l/osu/hud/HUDElement.kt | 9 + .../osu/hud/editor/HUDElementOverlay.kt | 178 ++++++++++++++++++ .../osu/hud/editor/HUDElementPreview.kt | 21 +-- .../osu/hud/editor/HUDElementToolbar.kt | 95 ---------- 8 files changed, 201 insertions(+), 121 deletions(-) create mode 100644 assets/rotate.png rename assets/{expand.png => scale.png} (100%) create mode 100644 src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt delete mode 100644 src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt diff --git a/assets/rotate.png b/assets/rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..0daca58dda9544ea71609f7e95b1d0615d8e0775 GIT binary patch literal 1774 zcmVPx*s7XXYRCr$PU2AgFFc4ljy#P}P&A)IG(v#4h1bUK|lMqgV`IQpraDm2RR<;t? zwXBt_$2!ZN%Bg-{A5KyWNm$J9A*MCPlB0%nSjg)WNv+pGI|x~>-o4vq#(1!K zb_8g^Yp}&80C*)pO$?HEi(=zOF zr$;gZcE5jbge>kobi6X6Qi|`Ww-p@E7YZN*N~tmZTs*Hq)*ey;;-ja8DI2kRfB7XY z^o>84q7jc-9}EOV(BiQ1Af%Ls;ySi8kR_l25xo?k&JY~DSRC$&?quzqRK-KAL3?;x z(QN~XfZhJjjR1k_FBw?p1TqjJ6xBrMo$C$y!CXsHk&&=%R;-l+W}5%9SG^Q~tV zmsiWn4}F8Cf-fc9rP8&%{cNIaic=7)uS5c#WZwX;mY1*ICxTx}QQdL~gpLwXR5vyv z1i-`l<>f6^(kSJE5jK-W<=bnY z{zM=^dUwWW04d)YvUk1`ADjSrC_cnmOiKy2Y3V{NDuXKWn|MJ9ur1K#1~5eXL#oI} z3IY<~Hv>X|lCqBB_`n^5fCP|Xh`PK>?!}0L15)7Z5#=C24SmY$YA7~v^#HRGfG65a z`w1f^XR=pKQV@xN-VK?NgLT)JBO46*y2%zqA%L*j={&e8t02=_kt_IE4@m`KLBi@F zm4s177)2kCp0v)6W;N)K%0hul zsfRrQ9^fCx?W2<^poJ{Mnt%Yt-#IV}2`;5B)&y8~+bHA1GOXhbwge2Xzf-ZV%lRyl zxwYV5nk@mp_D}bor<-4)!Pyr47X$$Cjk?(q@VI}%jSt(wS*q-_wFCbP0nm4jmC9bs za*PQGw*bibJStlP#spy989Ni;A;STJuv0{A`Iy9`d%<4eD*!?QYX21`O&ppH(zd^zNE+eK%VJnTHisBiN4hr{4NPVoC)aY&c z)%ZIRVCX2@brzDF1OS2d9Xid?++Q~W44F=*V(?6a#36vT5fp>~?z>JlNr<6>uuaMW zpc?`8H?+Nh(UcEbopMKD%CsPP>J;a}3BXDRcB&?C3v802|4&s}Q3x1X9+oTsa1Q1ja7MYYJ*wl_%a&BdU$xgsIk>Cn`t%ftI zptgt;_HS8#xkBiF?@4Pm1Sm-$_{(@uDs__VAzNTe%B0^lZL;tpfSwFByEM3L_z&%H z^+(9G6@k%SP=LrWdVc0!?~Nd-&3o2OexKCNx=+yvQ0j<~gY!k;mM(cW_5K3V5_@Mq zVNFeLj$@BXfCmZ6WfcJ1P`4m?WK36cBGrN3=mcmI@G2@>Zf1izs|k_t6R)LGKlnUf zl={o`<0TGwfdf8Ou^Dj-lU3#T26uh$B_TjJ>|*pn4w70|j>82>2{;nrf1L>xOUH6@ zD-VlXpIi@)4ZSL!g@B`4S{yb4!fu4S0D?0<=8YYczGNey-MPf7Df(3@6yTG%p)MV1 zOGPIgjcYZ1ZFd=;OZkk(=u$%-ul&c&?{H+htHurwj9+JB93Lhrd6%NfH z)&zu@`rPZ73CPUQOu$S)W&|=fKTE(&KxPCoH$O|jOh9G?GB-a + + val scaleDelta = min(-deltaX, deltaY) / 100f + + element.scaleX = (element.scaleX + scaleDelta).coerceIn(0.5f, 5f) + element.scaleY = (element.scaleY + scaleDelta).coerceIn(0.5f, 5f) + } + + + init { + isVisible = false + setSize(Config.getRES_WIDTH().toFloat(), Config.getRES_HEIGHT().toFloat()) + + attachChild(elementProxy) + attachChild(toolbar) + attachChild(scaleTip) + + toolbar.anchor = Anchor.TopCenter + toolbar.origin = Anchor.BottomCenter + addConstraint(toolbar, elementProxy) + + scaleTip.anchor = Anchor.BottomLeft + scaleTip.origin = Anchor.BottomLeft + scaleTip.x = -(TIP_SIZE / 2) + scaleTip.y = TIP_SIZE / 2 + addConstraint(scaleTip, elementProxy) + } + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (!isVisible) { + return false + } + return super.onAreaTouched(event, localX, localY) + } + + override fun onManagedUpdate(pSecondsElapsed: Float) { + + elementProxy.anchor = element.anchor + elementProxy.origin = element.origin + elementProxy.x = element.x + elementProxy.y = element.y + elementProxy.width = element.drawWidth * element.scaleX + elementProxy.height = element.drawHeight * element.scaleY + + super.onManagedUpdate(pSecondsElapsed) + } + + + private inner class Tip(texture: String, val onMove: (deltaX: Float, deltaY: Float) -> Unit) : Container() { + + init { + setSize(TIP_SIZE, TIP_SIZE) + + background = RoundedBox().apply { + cornerRadius = TIP_SIZE / 2 + color = ColorARGB(0xFF181825) + } + + attachChild(ExtendedSprite().apply { + textureRegion = ResourceManager.getInstance().getTexture(texture) + anchor = Anchor.Center + origin = Anchor.Center + relativeSizeAxes = Axes.Both + setSize(0.8f, 0.8f) + }) + } + + + private var initialX = 0f + private var initialY = 0f + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + + if (event.isActionDown) { + initialX = localX + initialY = localY + return true + } + + if (event.isActionMove) { + onMove(localX - initialX, localY - initialY) + return true + } + + return false + } + } + + private inner class Button(texture: String, back: ColorARGB, val action: () -> Unit) : Container() { + + init { + setSize(BUTTON_SIZE, BUTTON_SIZE) + + background = RoundedBox().apply { + cornerRadius = 12f + color = back + } + + attachChild(ExtendedSprite().apply { + textureRegion = ResourceManager.getInstance().getTexture(texture) + anchor = Anchor.Center + origin = Anchor.Center + relativeSizeAxes = Axes.Both + setSize(0.8f, 0.8f) + }) + + } + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { + if (event.isActionUp) { + action() + return true + } + + if (event.isActionDown) { + return true + } + + return false + } + } + + + companion object { + private const val TIP_SIZE = 32f + private const val BUTTON_SIZE = 46f + } + +} \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt index 51be6c80b..3031abdbd 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt @@ -61,36 +61,21 @@ class HUDElementPreview(private val element: HUDElement, val hud: GameplayHUD): //region Input handling private var initialX = 0f private var initialY = 0f - private var initialTime = 0L - private var wasMoved = false override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { if (event.isActionDown) { initialX = localX initialY = localY - initialTime = System.currentTimeMillis() - - clearEntityModifiers() - scaleTo(0.9f, 0.1f) - return true - } - - if (event.isActionMove) { - initialTime = System.currentTimeMillis() - clearEntityModifiers() - scaleTo(1f, 0.1f) } if (event.isActionUp) { - clearEntityModifiers() - scaleTo(1f, 0.1f) - wasMoved = abs(localX - initialX) > 1f && abs(localY - initialY) > 1f + if (abs(localX - initialX) < 1f && abs(localY - initialY) < 1f) { + clearEntityModifiers() + scaleTo(0.9f, 0.1f).scaleTo(1f, 0.1f) - if (!wasMoved && System.currentTimeMillis() - initialTime > 50) { hud.addElement(HUDElementSkinData(element::class)) - return false } } diff --git a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt b/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt deleted file mode 100644 index 5671b25e8..000000000 --- a/src/com/reco1l/osu/hud/editor/HUDElementToolbar.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.reco1l.osu.hud.editor - -import com.reco1l.andengine.Anchor -import com.reco1l.andengine.Axes -import com.reco1l.andengine.container.Container -import com.reco1l.andengine.container.LinearContainer -import com.reco1l.andengine.container.Orientation -import com.reco1l.andengine.shape.RoundedBox -import com.reco1l.andengine.sprite.ExtendedSprite -import com.reco1l.framework.ColorARGB -import com.reco1l.osu.hud.HUDElement -import com.reco1l.osu.updateThread -import org.anddev.andengine.input.touch.TouchEvent -import ru.nsu.ccfit.zuev.osu.ResourceManager - -class HUDElementToolbar(private val element: HUDElement) : LinearContainer() { - - init { - orientation = Orientation.Horizontal - isVisible = false - spacing = 4f - - attachChild(Button("delete", ColorARGB(0xFF260000)) { - updateThread { - element.remove() - } - }) - - attachChild(Button("rotate_left", ColorARGB(0xFF181825)) { - element.rotation -= 90f - }) - - attachChild(Button("rotate_right", ColorARGB(0xFF181825)) { - element.rotation += 90f - }) - - onMeasureContentSize() - } - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (!isVisible) { - return false - } - return super.onAreaTouched(event, localX, localY) - } - - override fun onManagedUpdate(pSecondsElapsed: Float) { - - x = element.drawX + element.drawWidth / 2 - drawWidth / 2 - - if (element.drawY < drawHeight) { - y = element.drawY + element.drawHeight + 4f - } else { - y = element.drawY - drawHeight - 4f - } - - super.onManagedUpdate(pSecondsElapsed) - } - - - inner class Button(texture: String, back: ColorARGB, val action: () -> Unit) : Container() { - - init { - setSize(46f, 46f) - - background = RoundedBox().apply { - cornerRadius = 12f - color = back - } - - attachChild(ExtendedSprite().apply { - textureRegion = ResourceManager.getInstance().getTexture(texture) - anchor = Anchor.Center - origin = Anchor.Center - relativeSizeAxes = Axes.Both - setSize(0.8f, 0.8f) - }) - - } - - override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (event.isActionUp) { - action() - return true - } - - if (event.isActionDown) { - return true - } - - return false - } - } - -} \ No newline at end of file From 272c948bbd609ee6d98f0131f99e566e8541382b Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Mon, 17 Feb 2025 22:19:39 -0300 Subject: [PATCH 22/85] Corrections in auto-anchor/origin system --- src/com/reco1l/osu/hud/HUDElement.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 33fac263e..8acb7a435 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -186,11 +186,18 @@ abstract class HUDElement : Container(), IGameplayEvents { val drawPosition = drawPosition val parentDrawSize = (parent as ExtendedEntity).drawSize - val relativeTopLeftDrawPosition = drawPosition / parentDrawSize + val relativeTopLeftDrawPosition = (drawPosition + drawSize / 2f) / parentDrawSize + val relativeTopRightDrawPosition = (drawPosition + Vec2(drawSize.x, 0f)) / parentDrawSize val relativeBottomRightDrawPosition = (drawPosition + drawSize) / parentDrawSize + val relativeBottomLeftDrawPosition = (drawPosition + Vec2(0f, drawSize.y)) / parentDrawSize val closest = Anchor.getAll().minBy { - min(abs(relativeTopLeftDrawPosition.distance(it)), abs(relativeBottomRightDrawPosition.distance(it))) + minOf( + abs(relativeTopLeftDrawPosition.distance(it)), + abs(relativeTopRightDrawPosition.distance(it)), + abs(relativeBottomRightDrawPosition.distance(it)), + abs(relativeBottomLeftDrawPosition.distance(it)) + ) } if (anchor != closest) { From 0e3b99c3c191373024906f50289a3e1c087ed9a7 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:25:21 +0700 Subject: [PATCH 23/85] Reword HUD parent layer of `GameplayHUD` --- src/com/reco1l/osu/hud/GameplayHUD.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index b4f99eb26..fd967d665 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -48,8 +48,8 @@ class GameplayHUD : Container(), IGameplayEvents { init { - // The engine we expect the HUD to be a effectively an instance of the AndEngine's HUD class. - // Since we need Container features we set a HUD instance as the parent and we just need to + // The engine expects the HUD to be an instance of AndEngine's HUD class. + // Since we need Container features, we set an HUD instance as the parent, and we just need to // reference the parent of this container to set the engine's HUD. val parent = HUD() parent.attachChild(this) From bf4fab5bdf83b4b94e2068621063c59cefc255c7 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:27:20 +0700 Subject: [PATCH 24/85] Remove "Restore" from HUD editor save dialog to avoid confusion --- src/com/reco1l/osu/hud/GameplayHUD.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index fd967d665..cbf0afbb5 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -91,7 +91,7 @@ class GameplayHUD : Container(), IGameplayEvents { ToastLogger.showText("Changes saved!", true) } } - .addButton("Discard & Restore") { + .addButton("Discard") { it.dismiss() updateThread { restore() From 32328da576a336ee23259756a2d8338424d6ab69 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:28:18 +0700 Subject: [PATCH 25/85] Fix `GameplayHUD.getSkinData` KDoc description --- src/com/reco1l/osu/hud/GameplayHUD.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index cbf0afbb5..550ab2433 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -130,7 +130,7 @@ class GameplayHUD : Container(), IGameplayEvents { } /** - * Saves the skin data of the HUD. + * Obtains the skin data of the HUD. */ fun getSkinData(): HUDSkinData { return HUDSkinData( From f92cea22f1d5c880effc373cd260a7e7405f6643 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:58:42 +0700 Subject: [PATCH 26/85] Reword default layout hardcode reasoning --- src/com/reco1l/osu/hud/GameplayHUD.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 550ab2433..c694ca699 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -160,9 +160,9 @@ class GameplayHUD : Container(), IGameplayEvents { } private fun applyDefaultLayout() { - // Default layout is hardcoded to keep the original layout of the game before the HUD - // editor was implemented. As well there's no other way since the original layout was - // using cross references between elements that are not possible to be set in the editor. + // The default layout is hardcoded to keep the original layout before the HUD editor was + // implemented, as it used cross-references between elements that are not possible to be + // set in the editor. val scoreCounter = getFirstOf() scoreCounter?.anchor = Anchor.TopRight From 7d2cae0bf961895b843063705b2001ec2eefbd27 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:10:55 +0700 Subject: [PATCH 27/85] Remove unused import --- src/com/reco1l/osu/hud/HUDElement.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 8acb7a435..167e6b7a6 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -30,7 +30,6 @@ import com.reco1l.osu.ui.entity.GameplayLeaderboard import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager import kotlin.math.abs -import kotlin.math.min /** From 3bf3dd85e5acdaf2ead6900be4924130e5209b39 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:14:56 +0700 Subject: [PATCH 28/85] Rename `HUDElementOverlay` to `HUDElementEditorOverlay` --- src/com/reco1l/osu/hud/HUDElement.kt | 16 ++++++++-------- ...mentOverlay.kt => HUDElementEditorOverlay.kt} | 7 +------ 2 files changed, 9 insertions(+), 14 deletions(-) rename src/com/reco1l/osu/hud/editor/{HUDElementOverlay.kt => HUDElementEditorOverlay.kt} (94%) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 167e6b7a6..8d0b9ef95 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -15,7 +15,7 @@ import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.editor.HUDElementOverlay +import com.reco1l.osu.hud.editor.HUDElementEditorOverlay import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDAverageOffsetCounter import com.reco1l.osu.hud.elements.HUDComboCounter @@ -59,7 +59,7 @@ abstract class HUDElement : Container(), IGameplayEvents { get() = this::class.simpleName!!.replace("HUD", "").replace("([a-z])([A-Z])".toRegex(), "$1 $2") - private var overlay: HUDElementOverlay? = null + private var editorOverlay: HUDElementEditorOverlay? = null private var connectionLine: Line? = null @@ -101,23 +101,23 @@ abstract class HUDElement : Container(), IGameplayEvents { if (value) { background = HUDElementBackground() - overlay = HUDElementOverlay(this) + editorOverlay = HUDElementEditorOverlay(this) - parent!!.attachChild(overlay!!) + parent!!.attachChild(editorOverlay!!) } else { connectionLine?.detachSelf() - overlay?.detachSelf() + editorOverlay?.detachSelf() connectionLine = null background = null - overlay = null + editorOverlay = null } } open fun onSelectionStateChange(isSelected: Boolean) { (background as? HUDElementBackground)?.isSelected = isSelected - overlay?.isVisible = isSelected + editorOverlay?.isVisible = isSelected if (isSelected) { updateConnectionLine() @@ -175,7 +175,7 @@ abstract class HUDElement : Container(), IGameplayEvents { override fun invalidateTransformations() { super.invalidateTransformations() - overlay?.invalidateTransformations() + editorOverlay?.invalidateTransformations() } diff --git a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementEditorOverlay.kt similarity index 94% rename from src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt rename to src/com/reco1l/osu/hud/editor/HUDElementEditorOverlay.kt index f8e0bfdd9..794cb2990 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementEditorOverlay.kt @@ -2,27 +2,22 @@ package com.reco1l.osu.hud.editor import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes -import com.reco1l.andengine.anchorOffsetX -import com.reco1l.andengine.anchorOffsetY import com.reco1l.andengine.container.ConstraintContainer import com.reco1l.andengine.container.Container import com.reco1l.andengine.container.LinearContainer import com.reco1l.andengine.container.Orientation -import com.reco1l.andengine.drawPosition import com.reco1l.andengine.shape.Box import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.sprite.ExtendedSprite import com.reco1l.framework.ColorARGB -import com.reco1l.framework.math.Vec2 import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.updateThread import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager -import kotlin.math.max import kotlin.math.min -class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() { +class HUDElementEditorOverlay(private val element: HUDElement) : ConstraintContainer() { private val elementProxy = Box().apply { color = ColorARGB.Transparent } From 4a4c7e4b3587b92b07d259aa78da286cdd55577d Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:20:36 +0700 Subject: [PATCH 29/85] Reword `nameText` inverted scale reason --- src/com/reco1l/osu/hud/HUDElement.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 8d0b9ef95..41aa6f234 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -289,8 +289,8 @@ abstract class HUDElement : Container(), IGameplayEvents { nameText.origin = Anchor.BottomLeft } - // The element might contain scale transformations. Those transformations are also applied - // to the background we might want to scale the text back to its original size. + // The element might contain scale transformations. Those transformations are also + // applied to the background, so we scale the text back to its original size. nameText.setScale(1f / this@HUDElement.scaleX, 1f / this@HUDElement.scaleY) // Same for the corner radius of the background. From 697c4ac3e289d9a6d2d4d4e64cb8b9a74a5f8e8a Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:43:44 +0700 Subject: [PATCH 30/85] Remove more unused import --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 1 - 1 file changed, 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 0cad601e1..1b50c5663 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -35,7 +35,6 @@ import com.reco1l.osu.hitobjects.SliderTickSprite; import com.reco1l.osu.hud.elements.HUDPPCounter; import com.reco1l.osu.ui.BlockAreaFragment; -import com.reco1l.osu.ui.MessageDialog; import com.reco1l.osu.ui.entity.GameplayLeaderboard; import com.reco1l.osu.multiplayer.Multiplayer; import com.reco1l.osu.multiplayer.RoomScene; From 3f66486e80d0cce25aea2806b330a696b3bcc8d8 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:44:23 +0700 Subject: [PATCH 31/85] Remove unused `isHudEditor` parameter --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 4 ++-- 1 file changed, 2 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 1b50c5663..44cc51c6e 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -378,7 +378,7 @@ private void setBackground() { } } - private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, final CoroutineScope scope, boolean isHudEditor) { + private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, final CoroutineScope scope) { if (!SecurityUtils.verifyFileIntegrity(GlobalManager.getInstance().getMainActivity())) { ToastLogger.showText(com.osudroid.resources.R.string.file_integrity_tampered, true); return false; @@ -689,7 +689,7 @@ public void startGame(BeatmapInfo beatmapInfo, String replayFile, boolean isHUDE boolean succeeded = false; try { - succeeded = loadGame(beatmapInfo != null ? beatmapInfo : lastBeatmapInfo, rfile, scope, isHUDEditor); + succeeded = loadGame(beatmapInfo != null ? beatmapInfo : lastBeatmapInfo, rfile, scope); if (succeeded) { prepareScene(); From 35ac6569e88daf97f7d5f8f333dfbedf57b62534 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 18 Feb 2025 10:34:00 -0300 Subject: [PATCH 32/85] Use common function to avoid double invalidation --- src/com/reco1l/andengine/Entities.kt | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/com/reco1l/andengine/Entities.kt b/src/com/reco1l/andengine/Entities.kt index 4f624817d..a17000cf6 100644 --- a/src/com/reco1l/andengine/Entities.kt +++ b/src/com/reco1l/andengine/Entities.kt @@ -36,8 +36,7 @@ fun IEntity?.getPaddedHeight() = when (this) { var ExtendedEntity.size get() = Vec2(width, height) set(value) { - width = value.x - height = value.y + setSize(value.x, value.y) } /** @@ -52,8 +51,7 @@ val ExtendedEntity.drawSize var ExtendedEntity.position get() = Vec2(x, y) set(value) { - x = value.x - y = value.y + setPosition(value.x, value.y) } /** @@ -155,11 +153,3 @@ fun IEntity?.getDrawY(): Float = when (this) { is IShape -> y else -> 0f } - -/** - * Attaches the entity to a parent. - */ -infix fun T.attachTo(parent: IEntity): T { - parent.attachChild(this) - return this -} \ No newline at end of file From 8bee8d7b3223252fcf1ac52c9fe13055e4b4a0d7 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 16:29:59 -0300 Subject: [PATCH 33/85] Remove input binding if condition fails --- src/com/reco1l/andengine/ExtendedEntity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index bebfa36a5..7a2a90aa6 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -865,6 +865,8 @@ abstract class ExtendedEntity( return false } return true + } else { + inputBindings[event.pointerID] = null } try { From 15b22acda8fe9da3f4c9d64b9a254323602e6a03 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 16:34:40 -0300 Subject: [PATCH 34/85] Simplify connection line positioning logic --- src/com/reco1l/osu/hud/HUDElement.kt | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 41aa6f234..270e92067 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -214,28 +214,16 @@ abstract class HUDElement : Container(), IGameplayEvents { private fun updateConnectionLine() { - val pointOnParent = Vec2( - parent!!.getDrawWidth() * anchor.x, - parent!!.getDrawHeight() * anchor.y - ) - - val pointOnChild = Vec2( - drawX + drawWidth * origin.x, - drawY + drawHeight * origin.y - ) - if (connectionLine == null) { connectionLine = Line().apply { - fromPoint = pointOnParent - toPoint = pointOnChild color = ColorARGB(0xFFF27272) lineWidth = 10f } parent!!.attachChild(connectionLine!!) - } else { - connectionLine!!.fromPoint = pointOnParent - connectionLine!!.toPoint = pointOnChild } + + connectionLine!!.fromPoint = anchorOffset + connectionLine!!.toPoint = drawPosition - originOffset } //endregion From ba7a09e2da617e8d92f66793358a868ed8dc2bae Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 16:35:10 -0300 Subject: [PATCH 35/85] Fix using center position as relative top left --- src/com/reco1l/osu/hud/HUDElement.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 270e92067..78b2e4ca5 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -185,17 +185,17 @@ abstract class HUDElement : Container(), IGameplayEvents { val drawPosition = drawPosition val parentDrawSize = (parent as ExtendedEntity).drawSize - val relativeTopLeftDrawPosition = (drawPosition + drawSize / 2f) / parentDrawSize - val relativeTopRightDrawPosition = (drawPosition + Vec2(drawSize.x, 0f)) / parentDrawSize - val relativeBottomRightDrawPosition = (drawPosition + drawSize) / parentDrawSize - val relativeBottomLeftDrawPosition = (drawPosition + Vec2(0f, drawSize.y)) / parentDrawSize + val relativeTopLeft = drawPosition / parentDrawSize + val relativeTopRight = (drawPosition + Vec2(drawSize.x, 0f)) / parentDrawSize + val relativeBottomRight = (drawPosition + drawSize) / parentDrawSize + val relativeBottomLeft = (drawPosition + Vec2(0f, drawSize.y)) / parentDrawSize val closest = Anchor.getAll().minBy { minOf( - abs(relativeTopLeftDrawPosition.distance(it)), - abs(relativeTopRightDrawPosition.distance(it)), - abs(relativeBottomRightDrawPosition.distance(it)), - abs(relativeBottomLeftDrawPosition.distance(it)) + abs(relativeTopLeft.distance(it)), + abs(relativeTopRight.distance(it)), + abs(relativeBottomRight.distance(it)), + abs(relativeBottomLeft.distance(it)) ) } From c689ca4042469669343bc7b9380906b88dc077d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ger=C3=B3nimo=20Ferruccio?= <103213881+Reco1I@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:37:51 -0300 Subject: [PATCH 36/85] Use forEachElement {} instead Co-authored-by: Rian (Reza Mouna Hendrian) <52914632+Rian8337@users.noreply.github.com> --- src/com/reco1l/osu/hud/GameplayHUD.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index c694ca699..66391300c 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -222,7 +222,7 @@ class GameplayHUD : Container(), IGameplayEvents { elementSelector = null } - mChildren?.filterIsInstance()?.forEach { it.setEditMode(value) } + forEachElement { it.setEditMode(value) } } //endregion From 2320a848e148bf380fa04c078fddf63f85c9251d Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 16:40:20 -0300 Subject: [PATCH 37/85] Make scale and rotation optional at JSON level --- src/com/reco1l/osu/hud/HUDSkinData.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 3301abba8..d5979eee3 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -64,8 +64,8 @@ data class HUDSkinData(val elements: List) { ), anchor = Anchor.getFromName(element.optString("anchor", "TopLeft")), origin = Anchor.getFromName(element.optString("origin", "TopLeft")), - scale = element.getDouble("scale").toFloat(), - rotation = element.getDouble("rotation").toFloat() + scale = element.optDouble("scale", 1.0).toFloat(), + rotation = element.optDouble("rotation", 1.0).toFloat() ) } From 5950a510c544b25755bef0b00fa240b11a5837a2 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 16:51:54 -0300 Subject: [PATCH 38/85] Use enum for HUDElements --- src/com/reco1l/osu/hud/HUDElement.kt | 37 ++++++++++++------- src/com/reco1l/osu/hud/HUDSkinData.kt | 2 +- .../osu/hud/editor/HUDElementSelector.kt | 2 +- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 78b2e4ca5..a05e0122d 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -27,27 +27,36 @@ import com.reco1l.osu.hud.elements.HUDPieSongProgress import com.reco1l.osu.hud.elements.HUDScoreCounter import com.reco1l.osu.hud.elements.HUDUnstableRateCounter import com.reco1l.osu.ui.entity.GameplayLeaderboard +import com.reco1l.toolkt.kotlin.capitalize import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.ResourceManager import kotlin.math.abs +import kotlin.reflect.KClass /** * List all the elements that can be added to the HUD. */ -val HUDElements = listOf( - HUDAccuracyCounter::class, - HUDComboCounter::class, - HUDHealthBar::class, - HUDPieSongProgress::class, - HUDPPCounter::class, - HUDScoreCounter::class, - HUDUnstableRateCounter::class, - HUDAverageOffsetCounter::class, - HUDHitErrorMeter::class, - HUDLinearSongProgress::class, - GameplayLeaderboard::class -) +@Suppress("EnumEntryName") +enum class HUDElements(val type: KClass) { + + accuracy_counter(HUDAccuracyCounter::class), + combo_counter(HUDComboCounter::class), + health_bar(HUDHealthBar::class), + pie_song_progress(HUDPieSongProgress::class), + pp_counter(HUDPPCounter::class), + score_counter(HUDScoreCounter::class), + ur_counter(HUDUnstableRateCounter::class), + avg_offset_counter(HUDAverageOffsetCounter::class), + hit_error_meter(HUDHitErrorMeter::class), + linear_song_progress(HUDLinearSongProgress::class), + leaderboard(GameplayLeaderboard::class); + + companion object { + operator fun get(type: KClass) = entries.first { it.type == type } + operator fun get(name: String) = valueOf(name) + } +} abstract class HUDElement : Container(), IGameplayEvents { @@ -56,7 +65,7 @@ abstract class HUDElement : Container(), IGameplayEvents { * Returns the name of this element. */ val name: String - get() = this::class.simpleName!!.replace("HUD", "").replace("([a-z])([A-Z])".toRegex(), "$1 $2") + get() = HUDElements[this::class].name.replace('_', ' ').capitalize() private var editorOverlay: HUDElementEditorOverlay? = null diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index d5979eee3..e5549a493 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -57,7 +57,7 @@ data class HUDSkinData(val elements: List) { val element = json.getJSONObject(i) HUDElementSkinData( - type = HUDElements.first { it.simpleName == element.getString("type") }, + type = HUDElements.valueOf(element.getString("type")).type, position = Vec2( element.optDouble("x", 0.0).toFloat(), element.optDouble("y", 0.0).toFloat(), diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index 441b80835..a7b32f432 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -25,7 +25,7 @@ import kotlin.reflect.full.primaryConstructor class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayEvents { - private val elements = HUDElements.map { it.primaryConstructor!!.call() } + private val elements = HUDElements.entries.map { it.type.primaryConstructor!!.call() } init { From 6e1eb5760c1819cdb973f2beba86a3aa53ef0e7b Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 17:07:08 -0300 Subject: [PATCH 39/85] Scale elements always from center --- src/com/reco1l/osu/hud/HUDElement.kt | 17 ++++++++++------- ...entEditorOverlay.kt => HUDElementOverlay.kt} | 10 +++++----- .../reco1l/osu/hud/editor/HUDElementPreview.kt | 3 +++ 3 files changed, 18 insertions(+), 12 deletions(-) rename src/com/reco1l/osu/hud/editor/{HUDElementEditorOverlay.kt => HUDElementOverlay.kt} (93%) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index a05e0122d..dc5484c5f 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -6,8 +6,6 @@ import com.reco1l.andengine.anchorOffset import com.reco1l.andengine.container.Container import com.reco1l.andengine.drawPosition import com.reco1l.andengine.drawSize -import com.reco1l.andengine.getDrawHeight -import com.reco1l.andengine.getDrawWidth import com.reco1l.andengine.originOffset import com.reco1l.andengine.position import com.reco1l.andengine.shape.Line @@ -15,7 +13,7 @@ import com.reco1l.andengine.shape.RoundedBox import com.reco1l.andengine.text.ExtendedText import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.hud.editor.HUDElementEditorOverlay +import com.reco1l.osu.hud.editor.HUDElementOverlay import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDAverageOffsetCounter import com.reco1l.osu.hud.elements.HUDComboCounter @@ -68,7 +66,7 @@ abstract class HUDElement : Container(), IGameplayEvents { get() = HUDElements[this::class].name.replace('_', ' ').capitalize() - private var editorOverlay: HUDElementEditorOverlay? = null + private var editorOverlay: HUDElementOverlay? = null private var connectionLine: Line? = null @@ -83,11 +81,12 @@ abstract class HUDElement : Container(), IGameplayEvents { origin = data.origin // We always expect to rotate around the center of the element. - rotationCenterX = 0.5f - rotationCenterY = 0.5f + setRotationCenter(0.5f, 0.5f) rotation = data.rotation + setScaleCenter(0.5f, 0.5f) setScale(data.scale) + setPosition(data.position.x, data.position.y) } } @@ -110,7 +109,7 @@ abstract class HUDElement : Container(), IGameplayEvents { if (value) { background = HUDElementBackground() - editorOverlay = HUDElementEditorOverlay(this) + editorOverlay = HUDElementOverlay(this) parent!!.attachChild(editorOverlay!!) } else { @@ -219,6 +218,10 @@ abstract class HUDElement : Container(), IGameplayEvents { origin = closest position -= originOffset - previousOriginOffset } + + // Restoring original scale/rotation center. + setRotationCenter(0.5f, 0.5f) + setScaleCenter(0.5f, 0.5f) } private fun updateConnectionLine() { diff --git a/src/com/reco1l/osu/hud/editor/HUDElementEditorOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt similarity index 93% rename from src/com/reco1l/osu/hud/editor/HUDElementEditorOverlay.kt rename to src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt index 794cb2990..71bc7e191 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementEditorOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt @@ -17,7 +17,7 @@ import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager import kotlin.math.min -class HUDElementEditorOverlay(private val element: HUDElement) : ConstraintContainer() { +class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() { private val elementProxy = Box().apply { color = ColorARGB.Transparent } @@ -79,10 +79,10 @@ class HUDElementEditorOverlay(private val element: HUDElement) : ConstraintConta override fun onManagedUpdate(pSecondsElapsed: Float) { - elementProxy.anchor = element.anchor - elementProxy.origin = element.origin - elementProxy.x = element.x - elementProxy.y = element.y + // We need to cancel scale center + elementProxy.x = element.drawX + (element.drawWidth * element.scaleCenterX) * (1f - element.scaleX) + elementProxy.y = element.drawY + (element.drawHeight * element.scaleCenterY) * (1f - element.scaleY) + elementProxy.width = element.drawWidth * element.scaleX elementProxy.height = element.drawHeight * element.scaleY diff --git a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt index 3031abdbd..71b01af41 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt @@ -49,12 +49,15 @@ class HUDElementPreview(private val element: HUDElement, val hud: GameplayHUD): override fun onManagedDraw(gl: GL10, camera: Camera) { // Scaling the element inside the box + element.setScaleCenter(0f, 0f) + if (element.drawWidth > element.drawHeight) { element.setScale(min(1f, getPaddedWidth() / element.drawWidth)) } else { element.setScale(min(1f, (getPaddedHeight() - label.drawHeight) / element.drawHeight)) } + super.onManagedDraw(gl, camera) } From 2057d3a8a3eefb7354023bcbd623fff096b94d63 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 21 Feb 2025 17:16:14 -0300 Subject: [PATCH 40/85] Replace rotation with flipping --- assets/flip.png | Bin 0 -> 1303 bytes assets/rotate.png | Bin 1774 -> 0 bytes assets/rotate_left.png | Bin 1455 -> 0 bytes assets/rotate_right.png | Bin 1484 -> 0 bytes src/com/reco1l/osu/hud/GameplayHUD.kt | 4 +- src/com/reco1l/osu/hud/HUDElement.kt | 7 +-- src/com/reco1l/osu/hud/HUDSkinData.kt | 11 +---- .../osu/hud/editor/HUDElementOverlay.kt | 42 ++++++++++-------- 8 files changed, 28 insertions(+), 36 deletions(-) create mode 100644 assets/flip.png delete mode 100644 assets/rotate.png delete mode 100644 assets/rotate_left.png delete mode 100644 assets/rotate_right.png diff --git a/assets/flip.png b/assets/flip.png new file mode 100644 index 0000000000000000000000000000000000000000..5766712eec07ea5855dcc92c19fe5fe09bdc8127 GIT binary patch literal 1303 zcmah{dpr|(0RQcQSyr2yM-P2)Zp)3hkTHv9n%8ji%qyWf)Y^w)5iO~Yqon&NBad=6 z%Xw96$F!O{@)*jS-Su%8)~+Pa$J$sY@z~6XK4+pR#-)GW(DCABZ&+2I=bCfZqYQ@xx7Ds{PUo28E<9+zh*1}` z>xEcsdbdr^@ti1|#gbA;?#$8+#yL8bXIdP5H+qO>D1kTNH&x%&*mbwzU*26G79QJ? zsy!aplwnrkkswege_~JDudnmIAYJ5@BqLOsec9XdBZw_wILxJ|V;gW7e5KP~a|i$z zZ;cAe7t^Cp{T5xnp>L1`GsUwRPLATq0aOG`-^$9s^S1=kKyyD_AWpEFR)yEH+MLUXDV?@ZcmKi{2iDkkkj z)ia>1rJ{aOH*|Cz@2AdvmaPr^$gSj4D+t*+kp3dh8~O)(0O1I3j75D zt-$#9BUvMmV&DCBW4crU7-V2mw2k;S$6TPfyjuyMgB7;Rm zO}6j(z4`q&I|fB*ntm%<2b!qX{ls8--DGgjQOe>1C){)j?pLm5NDvG7p30>;ZG!>dmS_X5|9f!1V(FUfsR4GC zbOL;Yzh1sViS)9i!#H>#|CWV-tET~y7(*C7@(;&|Ui!SO%qy#eIa`%V-XK5)?`Plp zmdNU3i-+ZkSGPj4$RD=8xW$k&p&Yb}ES`{u)`> zzD&{%pMelD3D|9FzzjWz zHv_AJ+h6gq0Hh85i9VGtpEvZWd8n7X)A!<~ndZlpbWy?eI6@5H^25QjR5@dM>c-oM zJ^D0T--)4T{Qb$exs$QX6g;8udD*^a8m-=8Tgj@wQD#&6Q?k{r)$OBIjynYO2klB# zlZa^uUX8UvqPCV@(l^{oL*{kZ5hC7qPc?Vz< zDJm8f%!bYfrKwNw1}nAPS6uMO1sL~RhFXGb9M8hf7c!Fk*lD*b&R-lVfk!RGiW^$| zqh|4(X&Xj2Gg)`-a5wfvV$8BGNMWV#(*i~25CCF>jFT3&!1t+IrOLpPT6H$?aPy(o IQKB;c1-c4JjsO4v literal 0 HcmV?d00001 diff --git a/assets/rotate.png b/assets/rotate.png deleted file mode 100644 index 0daca58dda9544ea71609f7e95b1d0615d8e0775..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVPx*s7XXYRCr$PU2AgFFc4ljy#P}P&A)IG(v#4h1bUK|lMqgV`IQpraDm2RR<;t? zwXBt_$2!ZN%Bg-{A5KyWNm$J9A*MCPlB0%nSjg)WNv+pGI|x~>-o4vq#(1!K zb_8g^Yp}&80C*)pO$?HEi(=zOF zr$;gZcE5jbge>kobi6X6Qi|`Ww-p@E7YZN*N~tmZTs*Hq)*ey;;-ja8DI2kRfB7XY z^o>84q7jc-9}EOV(BiQ1Af%Ls;ySi8kR_l25xo?k&JY~DSRC$&?quzqRK-KAL3?;x z(QN~XfZhJjjR1k_FBw?p1TqjJ6xBrMo$C$y!CXsHk&&=%R;-l+W}5%9SG^Q~tV zmsiWn4}F8Cf-fc9rP8&%{cNIaic=7)uS5c#WZwX;mY1*ICxTx}QQdL~gpLwXR5vyv z1i-`l<>f6^(kSJE5jK-W<=bnY z{zM=^dUwWW04d)YvUk1`ADjSrC_cnmOiKy2Y3V{NDuXKWn|MJ9ur1K#1~5eXL#oI} z3IY<~Hv>X|lCqBB_`n^5fCP|Xh`PK>?!}0L15)7Z5#=C24SmY$YA7~v^#HRGfG65a z`w1f^XR=pKQV@xN-VK?NgLT)JBO46*y2%zqA%L*j={&e8t02=_kt_IE4@m`KLBi@F zm4s177)2kCp0v)6W;N)K%0hul zsfRrQ9^fCx?W2<^poJ{Mnt%Yt-#IV}2`;5B)&y8~+bHA1GOXhbwge2Xzf-ZV%lRyl zxwYV5nk@mp_D}bor<-4)!Pyr47X$$Cjk?(q@VI}%jSt(wS*q-_wFCbP0nm4jmC9bs za*PQGw*bibJStlP#spy989Ni;A;STJuv0{A`Iy9`d%<4eD*!?QYX21`O&ppH(zd^zNE+eK%VJnTHisBiN4hr{4NPVoC)aY&c z)%ZIRVCX2@brzDF1OS2d9Xid?++Q~W44F=*V(?6a#36vT5fp>~?z>JlNr<6>uuaMW zpc?`8H?+Nh(UcEbopMKD%CsPP>J;a}3BXDRcB&?C3v802|4&s}Q3x1X9+oTsa1Q1ja7MYYJ*wl_%a&BdU$xgsIk>Cn`t%ftI zptgt;_HS8#xkBiF?@4Pm1Sm-$_{(@uDs__VAzNTe%B0^lZL;tpfSwFByEM3L_z&%H z^+(9G6@k%SP=LrWdVc0!?~Nd-&3o2OexKCNx=+yvQ0j<~gY!k;mM(cW_5K3V5_@Mq zVNFeLj$@BXfCmZ6WfcJ1P`4m?WK36cBGrN3=mcmI@G2@>Zf1izs|k_t6R)LGKlnUf zl={o`<0TGwfdf8Ou^Dj-lU3#T26uh$B_TjJ>|*pn4w70|j>82>2{;nrf1L>xOUH6@ zD-VlXpIi@)4ZSL!g@B`4S{yb4!fu4S0D?0<=8YYczGNey-MPf7Df(3@6yTG%p)MV1 zOGPIgjcYZ1ZFd=;OZkk(=u$%-ul&c&?{H+htHurwj9+JB93Lhrd6%NfH z)&zu@`rPZ73CPUQOu$S)W&|=fKTE(&KxPCoH$O|jOh9G?GB-aPx)X-PyuRCr$Po$YZPAq#4)3`YRl#Sp3H~^FtP;UNszyYAFfO7N40}g;O1)OuAjWOSE zSHBozK8!JcjWMs*TKIXJV@nV4;{Upd{xwnh#ajEijruo>*$zNOe9-;FTKjYW2r1AI zA6orut^Fp{yvS|0@c@MQAkee5_Dw`7p_^_204e@<)(=dV2XJNamj`fl@s|eh2;wgb z;8Db18Ni=Rl9i4_AoC4J$bR4bf82k!*2d=ujbmuHU9J;$&SAvIe(O-8cwcpRx*`CF zRk!mzfN=-1Qn})h0K~ii#M=rWRs_Hqf?7_Z4q`1B-N0D!R0 zxgW+DC^y#*m9)JeN7ymE(o|dXH%%+#Kmjr->09Fgdx9m{U=Mn$`pa4+p0e!03{Tn0+-v|6@q_oV zh*EOpDB1eNSy*d?qv0@@`yQMCN<}nwx5dO($~lM`Ikx35EC34DA2eWlSc<5<*LnzG zRJlBX%?r2y)Jn)tk{xodus!ED42#9H$p9Li7Ry8Sh{k1-IOFr{qZ&E}WNge5wprUJ?*RA&E%7aj}`Ln{C-=><~dcM1TJ z@~pCv7n_&z_+bFP8hV-4a4qnaQuJ^o6xyT$C{+h96^0s1Q-{Vmp0hgt{-TG9`eX1HY^qo@L?#o z1;FrBotE{t#P@qhtoM>$1OZCZpYHCp*-qw&*Hqa;4{INwWv!g;BE%QTYuG{%rx>&E zg$O(FNFsgN!vjEUQ6MHh>nNz#YQ5FLZdS0TCtIh6eGbICz~NRRmap0eWD=Zd3wx2f zqXd1(wVswiObwG@EJW@8Ad!mAELQ&UWqIM%itZq{Nd(}&RPy+2uL38ktB zq~y8E^QlwoYovyPs-oCl14kzKDl z0F=oc00)4w0?N%F4>$mn6;N*ec)$UmtblU!#{&)kWd)R*{|}2huzwZtwp9QC002ov JPDHLkV1f?xm;V3& diff --git a/assets/rotate_right.png b/assets/rotate_right.png deleted file mode 100644 index bc4540c1919d1cd31bd5b38b4b2e3dfb6358ab02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1484 zcmV;-1vC1IP)Px)hDk(0RCr$PodI$dAq+<0B-)eMoZ9y4Zv6dI-Syy{ zdvVS^JLev~_wfC4pXDqd6F(Ol#J^h+!ioTXIp;o}boC`6tO(%8o7=fXe9dwYRs`_H zIroil_Dexn5dgTK&v6jOI4lEUIRKz|2;njimIQDm2+IPv3WTKrTmixo0HV(azYkv@ z;>eqRh9C3q(Sg}}e>2-uc}f78;GYW7POY9#-uwI7n|fY94S+1Nfk_rN(UNNMGMg1l z0RYMGNX0(A-+MpBGpELxHfE&o@qwSc_jg^$HWq3EAcO!e2GR*6CB0*cA$KP{(G_%}kd;X9ph$#{X4{N%pn>qRxX1UXg z-_-)5+kxdn>QfySGRs{;{N4ac18!4VAcO#839O1?pJl}F27nRmfUjq}oLuR*Fg9F8 z{4M|(alWXB5*8pTw=^>Y*Vkj)i|RapF*u4vI;o>7dMXKhGT`T%0RGHP-ttM0wdG*f zYXhMC{)1M-<3J2^iVs?4o(Y5u_^QCw0E7^z?IP9Dj#dwoltZ{vwdc_UfbLkEEeFqq zQgEqacd9lB7ka-+L%E`9URRiNs4VWNSO5_ks+p>$ur+1%?p$I3y&G5~R*!27Rv_+R z8UV1!N8Gp`g0_@NbTY9U(g1+XOqccjCvyQ@(q zu-P9DQ*fe+HUMK75&+o5q&Xxr*21Lv4ZuY9f0b?k3j^4LumU`Z1-#oZR6+N@MQi|- zY98KYOZ!@z&nl7tz)}&HS%^7f6GAv9AZ@atz0$Ll9dhX9J$B_uEPz@iy5wBor1z%| z&^-HZ1^_h^>DkNWg9@7u#%Dt#04x%^+&DnEAJMZ)E#Td_p&sr>yeJ>709%&4%)(4z zi(<3;(E@;lqvFMip&<@Y8TG=6boo`}f}#abZfJQcC=9;M(NE$n1`r`&d7+`ya=tu1PRmXr- zSixkM91l(=V4{iFv2&?eKn8(&k$RaEZL{YAi-s2h;Yb`9&(z+KR2>5j4aQs%T(%s9 zo-R?{0vJ(If>3opM^6zf%kBaoLg1WouKLomrI4+4cLTtL0GGkROb}HxFYC2Fe73wu zp32o)KIgu!04gA;9|1r;JaTBD$%L^xfCvFwH#q9l6$In7Mz#2!)BC$bL0HAn+^Og& zzcrnZ__Os`n?qDq`hySY^5H9aLoA?CjC32oNZUV(2l-`D7+3Th{B) { put("anchor", Anchor.getName(it.anchor)) put("origin", Anchor.getName(it.origin)) put("scale", it.scale) - put("rotation", it.rotation) } } } @@ -64,8 +63,7 @@ data class HUDSkinData(val elements: List) { ), anchor = Anchor.getFromName(element.optString("anchor", "TopLeft")), origin = Anchor.getFromName(element.optString("origin", "TopLeft")), - scale = element.optDouble("scale", 1.0).toFloat(), - rotation = element.optDouble("rotation", 1.0).toFloat() + scale = element.optDouble("scale", 1.0).toFloat() ) } @@ -101,10 +99,5 @@ data class HUDElementSkinData( /** * The scale applied to the element. */ - val scale: Float = 1f, - - /** - * The rotation of the element. - */ - val rotation: Float = 0f + val scale: Float = 1f ) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt index 71bc7e191..7e90db743 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt @@ -15,7 +15,9 @@ import com.reco1l.osu.updateThread import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager +import kotlin.math.abs import kotlin.math.min +import kotlin.math.sign class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() { @@ -32,13 +34,15 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } }) - attachChild(Button("rotate_left", ColorARGB(0xFF181825)) { - element.rotation -= 90f + // Flip horizontally + attachChild(Button("flip", ColorARGB(0xFF181825)) { + element.scaleX = -element.scaleX }) - attachChild(Button("rotate_right", ColorARGB(0xFF181825)) { - element.rotation += 90f - }) + // Flip vertically + attachChild(Button("flip", ColorARGB(0xFF181825)) { + element.scaleY = -element.scaleY + }.apply { icon.rotation = -90f }) } @@ -46,8 +50,8 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() val scaleDelta = min(-deltaX, deltaY) / 100f - element.scaleX = (element.scaleX + scaleDelta).coerceIn(0.5f, 5f) - element.scaleY = (element.scaleY + scaleDelta).coerceIn(0.5f, 5f) + element.scaleX = (abs(element.scaleX) + scaleDelta).coerceIn(0.5f, 5f) * sign(element.scaleX) + element.scaleY = (abs(element.scaleY) + scaleDelta).coerceIn(0.5f, 5f) * sign(element.scaleY) } @@ -80,11 +84,11 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() override fun onManagedUpdate(pSecondsElapsed: Float) { // We need to cancel scale center - elementProxy.x = element.drawX + (element.drawWidth * element.scaleCenterX) * (1f - element.scaleX) - elementProxy.y = element.drawY + (element.drawHeight * element.scaleCenterY) * (1f - element.scaleY) + elementProxy.x = element.drawX + (element.drawWidth * element.scaleCenterX) * (1f - abs(element.scaleX)) + elementProxy.y = element.drawY + (element.drawHeight * element.scaleCenterY) * (1f - abs(element.scaleY)) - elementProxy.width = element.drawWidth * element.scaleX - elementProxy.height = element.drawHeight * element.scaleY + elementProxy.width = element.drawWidth * abs(element.scaleX) + elementProxy.height = element.drawHeight * abs(element.scaleY) super.onManagedUpdate(pSecondsElapsed) } @@ -132,6 +136,14 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() private inner class Button(texture: String, back: ColorARGB, val action: () -> Unit) : Container() { + val icon = ExtendedSprite().apply { + textureRegion = ResourceManager.getInstance().getTexture(texture) + anchor = Anchor.Center + origin = Anchor.Center + relativeSizeAxes = Axes.Both + setSize(0.8f, 0.8f) + } + init { setSize(BUTTON_SIZE, BUTTON_SIZE) @@ -140,13 +152,7 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() color = back } - attachChild(ExtendedSprite().apply { - textureRegion = ResourceManager.getInstance().getTexture(texture) - anchor = Anchor.Center - origin = Anchor.Center - relativeSizeAxes = Axes.Both - setSize(0.8f, 0.8f) - }) + attachChild(icon) } From d1c090544f8e80fd873f33722d9a511362facfd3 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 15:55:27 -0300 Subject: [PATCH 41/85] Implement multiple scale tips --- src/com/reco1l/andengine/Entities.kt | 28 +++++ src/com/reco1l/andengine/shape/OutlineBox.kt | 60 +++++++++ src/com/reco1l/framework/math/Vec2.kt | 5 +- src/com/reco1l/osu/hud/GameplayHUD.kt | 8 +- src/com/reco1l/osu/hud/HUDElement.kt | 110 ++++++----------- src/com/reco1l/osu/hud/HUDSkinData.kt | 10 +- .../osu/hud/editor/HUDElementOverlay.kt | 116 ++++++++++++------ .../osu/hud/editor/HUDElementSelector.kt | 48 ++++++-- 8 files changed, 250 insertions(+), 135 deletions(-) create mode 100644 src/com/reco1l/andengine/shape/OutlineBox.kt diff --git a/src/com/reco1l/andengine/Entities.kt b/src/com/reco1l/andengine/Entities.kt index a17000cf6..fc6e413d6 100644 --- a/src/com/reco1l/andengine/Entities.kt +++ b/src/com/reco1l/andengine/Entities.kt @@ -61,6 +61,34 @@ val ExtendedEntity.drawPosition get() = Vec2(drawX, drawY) +/** + * The scale of the entity. + */ +var ExtendedEntity.scaleVec + get() = Vec2(scaleX, scaleY) + set(value) { + setScale(value.x, value.y) + } + +/** + * The center where the entity will scale from. + */ +var ExtendedEntity.scaleCenter + get() = Vec2(scaleCenterX, scaleCenterY) + set(value) { + setScaleCenter(value.x, value.y) + } + +/** + * The center where the entity will rotate from. + */ +var ExtendedEntity.rotationCenter + get() = Vec2(rotationCenterX, rotationCenterY) + set(value) { + setRotationCenter(value.x, value.y) + } + + /** * The total offset applied to the entity. */ diff --git a/src/com/reco1l/andengine/shape/OutlineBox.kt b/src/com/reco1l/andengine/shape/OutlineBox.kt new file mode 100644 index 000000000..9146ea449 --- /dev/null +++ b/src/com/reco1l/andengine/shape/OutlineBox.kt @@ -0,0 +1,60 @@ +package com.reco1l.andengine.shape + +import com.reco1l.andengine.* +import org.anddev.andengine.engine.camera.* +import org.anddev.andengine.opengl.util.* +import org.anddev.andengine.opengl.vertex.* +import javax.microedition.khronos.opengles.* +import javax.microedition.khronos.opengles.GL10.* + +open class OutlineBox : ExtendedEntity(vertexBuffer = OutlineBoxVertexBuffer()) { + + /** + * The width of the line. + */ + var lineWidth = 1f + + + override fun onInitDraw(pGL: GL10) { + super.onInitDraw(pGL) + + GLHelper.disableCulling(pGL) + GLHelper.disableTextures(pGL) + GLHelper.disableTexCoordArray(pGL) + + pGL.glLineWidth(lineWidth) + } + + + override fun onUpdateVertexBuffer() { + (vertexBuffer as OutlineBoxVertexBuffer).update(drawWidth, drawHeight) + } + + override fun drawVertices(gl: GL10, camera: Camera) { + gl.glDrawArrays(GL_LINE_LOOP, 0, 4) + } + + + class OutlineBoxVertexBuffer : VertexBuffer(4 * 2, GL11.GL_STATIC_DRAW, false) { + + fun update(width: Float, height: Float) { + floatBuffer.apply { + put(0, 0f) + put(1, 0f) + + put(2, width) + put(3, 0f) + + put(4, width) + put(5, height) + + put(6, 0f) + put(7, height) + } + + setHardwareBufferNeedsUpdate() + } + + } + +} \ No newline at end of file diff --git a/src/com/reco1l/framework/math/Vec2.kt b/src/com/reco1l/framework/math/Vec2.kt index d5dee8474..36aeecc60 100644 --- a/src/com/reco1l/framework/math/Vec2.kt +++ b/src/com/reco1l/framework/math/Vec2.kt @@ -1,8 +1,7 @@ package com.reco1l.framework.math import com.reco1l.toolkt.toDegrees -import kotlin.math.atan2 -import kotlin.math.hypot +import kotlin.math.* data class Vec2(val x: Float, val y: Float) { @@ -29,7 +28,7 @@ data class Vec2(val x: Float, val y: Float) { operator fun unaryMinus() = Vec2(-x, -y) fun distance(other: Vec2) = hypot(x - other.x, y - other.y) - fun angleDeg(other: Vec2) = atan2(other.y - y, other.x - x).toDouble().toDegrees().toFloat() + fun absolute() = Vec2(x.absoluteValue, y.absoluteValue) override fun equals(other: Any?): Boolean { return this === other || other is Vec2 && x == other.x && y == other.y diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 6f6680868..18bf8b984 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -73,9 +73,9 @@ class GameplayHUD : Container(), IGameplayEvents { fun onBackPress() { - fun restore() { - x = 0f - width = Config.getRES_WIDTH().toFloat() + if (elementSelector?.isExpanded == true) { + elementSelector?.collapse() + return } MessageDialog() @@ -85,7 +85,6 @@ class GameplayHUD : Container(), IGameplayEvents { it.dismiss() updateThread { ToastLogger.showText("Saving changes...", true) - restore() setEditMode(false) saveToSkinJSON() ToastLogger.showText("Changes saved!", true) @@ -94,7 +93,6 @@ class GameplayHUD : Container(), IGameplayEvents { .addButton("Discard") { it.dismiss() updateThread { - restore() setEditMode(false) setSkinData(OsuSkin.get().hudSkinData) ToastLogger.showText("Changes discarded!", true) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 2a867e1c3..29f238594 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -1,16 +1,8 @@ package com.reco1l.osu.hud -import com.reco1l.andengine.Anchor -import com.reco1l.andengine.ExtendedEntity -import com.reco1l.andengine.anchorOffset +import com.reco1l.andengine.* import com.reco1l.andengine.container.Container -import com.reco1l.andengine.drawPosition -import com.reco1l.andengine.drawSize -import com.reco1l.andengine.originOffset -import com.reco1l.andengine.position -import com.reco1l.andengine.shape.Line -import com.reco1l.andengine.shape.RoundedBox -import com.reco1l.andengine.text.ExtendedText +import com.reco1l.andengine.shape.* import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 import com.reco1l.osu.hud.editor.HUDElementOverlay @@ -27,7 +19,6 @@ import com.reco1l.osu.hud.elements.HUDUnstableRateCounter import com.reco1l.osu.ui.entity.GameplayLeaderboard import com.reco1l.toolkt.kotlin.capitalize import org.anddev.andengine.input.touch.TouchEvent -import ru.nsu.ccfit.zuev.osu.ResourceManager import kotlin.math.abs import kotlin.reflect.KClass @@ -81,7 +72,7 @@ abstract class HUDElement : Container(), IGameplayEvents { origin = data.origin setScaleCenter(0.5f, 0.5f) - setScale(data.scale) + setScale(data.scale.x, data.scale.y) setPosition(data.position.x, data.position.y) } @@ -91,7 +82,7 @@ abstract class HUDElement : Container(), IGameplayEvents { type = this::class, anchor = anchor, origin = origin, - scale = scaleX, // Scale is uniform currently. + scale = scaleVec, position = Vec2(x, y) ) @@ -103,7 +94,10 @@ abstract class HUDElement : Container(), IGameplayEvents { isInEditMode = value if (value) { - background = HUDElementBackground() + background = Box().apply { + color = ColorARGB(0x29F27272) + alpha = 0.25f + } editorOverlay = HUDElementOverlay(this) parent!!.attachChild(editorOverlay!!) @@ -119,7 +113,7 @@ abstract class HUDElement : Container(), IGameplayEvents { open fun onSelectionStateChange(isSelected: Boolean) { - (background as? HUDElementBackground)?.isSelected = isSelected + background?.alpha = if (isSelected) 0.5f else 0.15f editorOverlay?.isVisible = isSelected if (isSelected) { @@ -157,10 +151,7 @@ abstract class HUDElement : Container(), IGameplayEvents { // Preventing from moving the element if it's not selected. if ((parent as? GameplayHUD)?.selected == this) { - applyClosestAnchorOrigin() - x += deltaX - y += deltaY - updateConnectionLine() + move(deltaX, deltaY) initialX = parentLocalX initialY = parentLocalY @@ -181,6 +172,9 @@ abstract class HUDElement : Container(), IGameplayEvents { editorOverlay?.invalidateTransformations() } + //endregion + + //region Edit mode private fun applyClosestAnchorOrigin() { @@ -214,8 +208,6 @@ abstract class HUDElement : Container(), IGameplayEvents { position -= originOffset - previousOriginOffset } - // Restoring original scale/rotation center. - setRotationCenter(0.5f, 0.5f) setScaleCenter(0.5f, 0.5f) } @@ -225,17 +217,39 @@ abstract class HUDElement : Container(), IGameplayEvents { connectionLine = Line().apply { color = ColorARGB(0xFFF27272) lineWidth = 10f + isVisible = false } parent!!.attachChild(connectionLine!!) } connectionLine!!.fromPoint = anchorOffset - connectionLine!!.toPoint = drawPosition - originOffset + connectionLine!!.toPoint = (editorOverlay?.outline?.drawPosition ?: Vec2.Zero) + drawSize * scaleVec.absolute() * origin } - //endregion + override fun setScaleX(pScaleX: Float) { + super.setScaleX(pScaleX) + updateConnectionLine() + } - //region Edit mode + override fun setScaleY(pScaleY: Float) { + super.setScaleY(pScaleY) + updateConnectionLine() + } + + override fun setScale(pScale: Float) { + super.setScale(pScale) + updateConnectionLine() + } + + /** + * Moves the element by the specified delta. + */ + fun move(deltaX: Float, deltaY: Float) { + applyClosestAnchorOrigin() + x += deltaX + y += deltaY + updateConnectionLine() + } /** * Removes the element from the HUD. @@ -245,53 +259,5 @@ abstract class HUDElement : Container(), IGameplayEvents { detachSelf() } - private inner class HUDElementBackground : Container() { - - - var isSelected = false - set(value) { - background!!.alpha = if (value) 0.5f else 0.15f - nameText.isVisible = value - field = value - } - - - private val nameText = ExtendedText().apply { - font = ResourceManager.getInstance().getFont("smallFont") - color = ColorARGB(0xFFF27272) - text = this@HUDElement.name - isVisible = false - } - - - init { - attachChild(nameText) - - background = RoundedBox().apply { - color = ColorARGB(0x29F27272) - alpha = 0.25f - } - } - - override fun onManagedUpdate(pSecondsElapsed: Float) { - - // Switch the text position according to the element's position. - if (this@HUDElement.drawY - drawHeight <= 0f) { - nameText.anchor = Anchor.BottomLeft - nameText.origin = Anchor.TopLeft - } else { - nameText.anchor = Anchor.TopLeft - nameText.origin = Anchor.BottomLeft - } - - // The element might contain scale transformations. Those transformations are also - // applied to the background, so we scale the text back to its original size. - nameText.setScale(1f / this@HUDElement.scaleX, 1f / this@HUDElement.scaleY) - - // Same for the corner radius of the background. - (background as RoundedBox).cornerRadius = 6f * (1f / this@HUDElement.scaleX) - } - - } //endregion } diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 4f8de40a1..9312aa4e2 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -44,7 +44,8 @@ data class HUDSkinData(val elements: List) { put("y", it.position.y) put("anchor", Anchor.getName(it.anchor)) put("origin", Anchor.getName(it.origin)) - put("scale", it.scale) + put("scaleX", it.scale.x) + put("scaleY", it.scale.y) } } } @@ -63,7 +64,10 @@ data class HUDSkinData(val elements: List) { ), anchor = Anchor.getFromName(element.optString("anchor", "TopLeft")), origin = Anchor.getFromName(element.optString("origin", "TopLeft")), - scale = element.optDouble("scale", 1.0).toFloat() + scale = Vec2( + element.optDouble("scaleX", 1.0).toFloat(), + element.optDouble("scaleY", 1.0).toFloat() + ) ) } @@ -99,5 +103,5 @@ data class HUDElementSkinData( /** * The scale applied to the element. */ - val scale: Float = 1f + val scale: Vec2 = Vec2.One ) \ No newline at end of file diff --git a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt index 7e90db743..86d5ba76f 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt @@ -6,25 +6,29 @@ import com.reco1l.andengine.container.ConstraintContainer import com.reco1l.andengine.container.Container import com.reco1l.andengine.container.LinearContainer import com.reco1l.andengine.container.Orientation -import com.reco1l.andengine.shape.Box -import com.reco1l.andengine.shape.RoundedBox +import com.reco1l.andengine.shape.* import com.reco1l.andengine.sprite.ExtendedSprite +import com.reco1l.andengine.text.* import com.reco1l.framework.ColorARGB import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.updateThread import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager -import kotlin.math.abs -import kotlin.math.min -import kotlin.math.sign +import kotlin.math.* class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() { - private val elementProxy = Box().apply { color = ColorARGB.Transparent } + val outline = OutlineBox().apply { + color = ColorARGB(0xFFF27272) + lineWidth = 8f + } + private val toolbar = LinearContainer().apply { + anchor = Anchor.TopCenter + origin = Anchor.BottomCenter orientation = Orientation.Horizontal spacing = 4f @@ -46,12 +50,12 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } - private val scaleTip = Tip("scale") { deltaX, deltaY -> - - val scaleDelta = min(-deltaX, deltaY) / 100f - - element.scaleX = (abs(element.scaleX) + scaleDelta).coerceIn(0.5f, 5f) * sign(element.scaleX) - element.scaleY = (abs(element.scaleY) + scaleDelta).coerceIn(0.5f, 5f) * sign(element.scaleY) + private val nameText = ExtendedText().apply { + anchor = Anchor.BottomCenter + origin = Anchor.TopCenter + font = ResourceManager.getInstance().getFont("smallFont") + color = ColorARGB(0xFFF27272) + text = element.name } @@ -59,57 +63,67 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() isVisible = false setSize(Config.getRES_WIDTH().toFloat(), Config.getRES_HEIGHT().toFloat()) - attachChild(elementProxy) + attachChild(outline) attachChild(toolbar) - attachChild(scaleTip) + attachChild(nameText) + + addConstraint(toolbar, outline) + addConstraint(nameText, outline) - toolbar.anchor = Anchor.TopCenter - toolbar.origin = Anchor.BottomCenter - addConstraint(toolbar, elementProxy) + val topLeftTip = Tip().apply { anchor = Anchor.BottomLeft } + val topRightTip = Tip().apply { anchor = Anchor.BottomRight } + val bottomLeftTip = Tip().apply { anchor = Anchor.TopLeft } + val bottomRightTip = Tip().apply { anchor = Anchor.TopRight } - scaleTip.anchor = Anchor.BottomLeft - scaleTip.origin = Anchor.BottomLeft - scaleTip.x = -(TIP_SIZE / 2) - scaleTip.y = TIP_SIZE / 2 - addConstraint(scaleTip, elementProxy) + attachChild(topLeftTip) + attachChild(topRightTip) + attachChild(bottomLeftTip) + attachChild(bottomRightTip) + + addConstraint(topLeftTip, outline) + addConstraint(topRightTip, outline) + addConstraint(bottomLeftTip, outline) + addConstraint(bottomRightTip, outline) } override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (!isVisible) { - return false + if (isVisible) { + return super.onAreaTouched(event, localX, localY) } - return super.onAreaTouched(event, localX, localY) + return false } override fun onManagedUpdate(pSecondsElapsed: Float) { // We need to cancel scale center - elementProxy.x = element.drawX + (element.drawWidth * element.scaleCenterX) * (1f - abs(element.scaleX)) - elementProxy.y = element.drawY + (element.drawHeight * element.scaleCenterY) * (1f - abs(element.scaleY)) + outline.x = element.drawX + (element.drawWidth * element.scaleCenterX) * (1f - abs(element.scaleX)) + outline.y = element.drawY + (element.drawHeight * element.scaleCenterY) * (1f - abs(element.scaleY)) - elementProxy.width = element.drawWidth * abs(element.scaleX) - elementProxy.height = element.drawHeight * abs(element.scaleY) + outline.width = element.drawWidth * abs(element.scaleX) + outline.height = element.drawHeight * abs(element.scaleY) super.onManagedUpdate(pSecondsElapsed) } - private inner class Tip(texture: String, val onMove: (deltaX: Float, deltaY: Float) -> Unit) : Container() { + + /** + * Represents a tip in the overlay toolbar. + */ + private inner class Tip : Container() { init { + origin = Anchor.Center setSize(TIP_SIZE, TIP_SIZE) - background = RoundedBox().apply { - cornerRadius = TIP_SIZE / 2 - color = ColorARGB(0xFF181825) - } - - attachChild(ExtendedSprite().apply { - textureRegion = ResourceManager.getInstance().getTexture(texture) + // The tip container is bigger than the actual tip in order to make it easier to touch. + attachChild(RoundedBox().apply { anchor = Anchor.Center origin = Anchor.Center + color = ColorARGB(0xFFF27272) + cornerRadius = TIP_SIZE relativeSizeAxes = Axes.Both - setSize(0.8f, 0.8f) + setSize(0.5f, 0.5f) }) } @@ -126,7 +140,22 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } if (event.isActionMove) { - onMove(localX - initialX, localY - initialY) + var deltaX = localX - initialX + var deltaY = localY - initialY + + if (anchor.x == 0f) { + deltaX = -deltaX + } + + if (anchor.y == 0f) { + deltaY = -deltaY + } + + val deltaScaleX = deltaX / 100f + val deltaScaleY = deltaY / 100f + + element.scaleX = (abs(element.scaleX) + deltaScaleX).coerceIn(0.5f, 5f).withSign(element.scaleX) + element.scaleY = (abs(element.scaleY) + deltaScaleY).coerceIn(0.5f, 5f).withSign(element.scaleY) return true } @@ -134,6 +163,11 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } } + + + /** + * Represents a button in the overlay toolbar. + */ private inner class Button(texture: String, back: ColorARGB, val action: () -> Unit) : Container() { val icon = ExtendedSprite().apply { @@ -159,6 +193,8 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { if (event.isActionUp) { action() + clearEntityModifiers() + scaleTo(0.9f, 0.1f).scaleTo(1f, 0.1f) return true } @@ -172,7 +208,7 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() companion object { - private const val TIP_SIZE = 32f + private const val TIP_SIZE = 36f private const val BUTTON_SIZE = 46f } diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index a7b32f432..a32fc8c79 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -25,6 +25,13 @@ import kotlin.reflect.full.primaryConstructor class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayEvents { + /** + * Whether the element selector is expanded. + */ + val isExpanded + get() = x >= 0f + + private val elements = HUDElements.entries.map { it.type.primaryConstructor!!.call() } @@ -67,19 +74,10 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE } if (event.isActionUp) { - - this@HUDElementSelector.clearEntityModifiers() - - if (this@HUDElementSelector.x < 0f) { - this@HUDElementSelector.moveToX(0f, 0.2f) - - hud.moveToX(SELECTOR_WIDTH, 0.2f) - hud.sizeToX(Config.getRES_WIDTH() - SELECTOR_WIDTH, 0.2f) + if (isExpanded) { + collapse() } else { - this@HUDElementSelector.moveToX(-SELECTOR_WIDTH, 0.2f) - - hud.moveToX(0f, 0.2f) - hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) + expand() } return false } @@ -116,6 +114,32 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE } + fun expand() { + if (isExpanded) { + return + } + clearEntityModifiers() + + isVisible = true + moveToX(0f, 0.2f) + + hud.moveToX(SELECTOR_WIDTH, 0.2f) + hud.sizeToX(Config.getRES_WIDTH() - SELECTOR_WIDTH, 0.2f) + } + + fun collapse() { + if (!isExpanded) { + return + } + clearEntityModifiers() + + moveToX(-SELECTOR_WIDTH, 0.2f).then { isVisible = false } + + hud.moveToX(0f, 0.2f) + hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) + } + + //region Gameplay Events override fun onNoteHit(statistics: StatisticV2) { From 51e069093ddc8fc2482f69767dfb8ece6270f20c Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 16:38:17 -0300 Subject: [PATCH 42/85] Reset gameplay if editor mode is still enabled --- src/com/reco1l/osu/hud/GameplayHUD.kt | 4 +- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 57 +++++++++++++++---- .../ccfit/zuev/osu/scoring/StatisticV2.java | 24 ++++++++ 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 18bf8b984..1b4acad8e 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -218,7 +218,9 @@ class GameplayHUD : Container(), IGameplayEvents { elementSelector = null } - forEachElement { it.setEditMode(value) } + + // Cannot use forEachElement {} because we're modifying the list. + mChildren?.filterIsInstance()?.fastForEach { it.setEditMode(value) } } //endregion diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index 44cc51c6e..2ba8d4db9 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -85,6 +85,7 @@ import java.util.*; import java.util.concurrent.CancellationException; +import javax.annotation.Nullable; import javax.microedition.khronos.opengles.GL10; import ru.nsu.ccfit.zuev.audio.Status; @@ -222,6 +223,11 @@ public class GameScene implements IUpdateHandler, GameObjectListener, */ public boolean isHUDEditorMode = false; + /** + * Whether the game started in HUD editor mode. + */ + public boolean startedFromHUDEditor = false; + // Timing @@ -378,13 +384,15 @@ private void setBackground() { } } - private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, final CoroutineScope scope) { + private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, @Nullable CoroutineScope scope) { if (!SecurityUtils.verifyFileIntegrity(GlobalManager.getInstance().getMainActivity())) { ToastLogger.showText(com.osudroid.resources.R.string.file_integrity_tampered, true); return false; } - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } if (rFile != null && rFile.startsWith("https://")) { this.replayFilePath = Config.getCachePath() + "/" + @@ -397,7 +405,9 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina } else this.replayFilePath = rFile; - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } boolean shouldParseBeatmap = parsedBeatmap == null || !parsedBeatmap.getMd5().equals(beatmapInfo.getMD5()); @@ -447,7 +457,9 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina breakPeriods = new LinkedList<>(); for (var period : playableBeatmap.getEvents().breaks) { - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } breakPeriods.add(new BreakPeriod(period.startTime / 1000f, period.endTime / 1000f)); } @@ -488,13 +500,17 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina GameHelper.setSpeedMultiplier(modMenu.getSpeed()); scene.setTimeMultiplier(GameHelper.getSpeedMultiplier()); - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } GlobalManager.getInstance().getSongService().preLoad(audioFilePath, GameHelper.getSpeedMultiplier(), GameHelper.getSpeedMultiplier() != 1f && (modMenu.isEnableNCWhenSpeedChange() || modMenu.getMod().contains(GameMod.MOD_NIGHTCORE))); - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } objects = new LinkedList<>(playableBeatmap.getHitObjects().objects); activeObjects = new LinkedList<>(); @@ -512,7 +528,9 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina comboColors = new ArrayList<>(); for (RGBColor color : playableBeatmap.getColors().comboColors) { - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } comboColors.add(new RGBColor(color.r() / 255, color.g() / 255, color.b() / 255)); } @@ -525,7 +543,9 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina comboColors.addAll(OsuSkin.get().getComboColor()); } - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } lastActiveObjectHitTime = 0; @@ -550,7 +570,9 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina GameObjectPool.getInstance().purge(); - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } FollowPointConnection.getPool().renew(16); SliderTickSprite.getPool().renew(16); @@ -580,7 +602,9 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, fina storyboardSprite.loadStoryboard(beatmapInfo.getPath()); } - JobKt.ensureActive(scope.getCoroutineContext()); + if (scope != null) { + JobKt.ensureActive(scope.getCoroutineContext()); + } GameObjectPool.getInstance().preload(); @@ -647,6 +671,7 @@ public void startGame(BeatmapInfo beatmapInfo, String replayFile) { public void startGame(BeatmapInfo beatmapInfo, String replayFile, boolean isHUDEditor) { isHUDEditorMode = isHUDEditor; + startedFromHUDEditor = isHUDEditor; scene = new ExtendedScene(); if (Config.isEnableStoryboard()) { @@ -1411,6 +1436,16 @@ public void onUpdate(final float pSecondsElapsed) { } if (shouldBePunished || (objects.isEmpty() && activeObjects.isEmpty() && leadOut > 2)) { + + // Reset the game to continue the HUD editor session. + if (startedFromHUDEditor && isHUDEditorMode) { + elapsedTime = initialElapsedTime; + loadGame(lastBeatmapInfo, null, null); + stat.reset(); + skip(); + return; + } + scene = new ExtendedScene(); engine.getCamera().setHUD(null); BeatmapSkinManager.setSkinEnabled(false); @@ -1451,7 +1486,7 @@ public void onUpdate(final float pSecondsElapsed) { blockAreaFragment = null; } - if (scoringScene != null) { + if (scoringScene != null && !startedFromHUDEditor) { if (replaying) { ModMenu.getInstance().setMod(Replay.oldMod); ModMenu.getInstance().setChangeSpeed(Replay.oldChangeSpeed); diff --git a/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java b/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java index ebdc16f14..7888717a2 100644 --- a/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java +++ b/src/ru/nsu/ccfit/zuev/osu/scoring/StatisticV2.java @@ -969,4 +969,28 @@ public void setPP(double value) { public double getPP() { return pp; } + + /** + * Resets the statistics to their initial values. + */ + public void reset() { + hit300 = 0; + hit100 = 0; + hit50 = 0; + hit300k = 0; + hit100k = 0; + misses = 0; + scoreMaxCombo = 0; + currentCombo = 0; + totalScore = 0; + hp = 1; + mark = null; + bonusScore = 0; + positiveTotalOffsetSum = 0; + positiveHitOffsetSum = 0; + negativeTotalOffsetSum = 0; + negativeHitOffsetSum = 0; + unstableRate = 0; + pp = 0; + } } From 93ba4208e79cc5de84a25aeb320807fb9347cabb Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 16:38:55 -0300 Subject: [PATCH 43/85] Remove unused asset --- assets/scale.png | Bin 687 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/scale.png diff --git a/assets/scale.png b/assets/scale.png deleted file mode 100644 index c6827c4f732f97c2d4fad29f5d32736c67619887..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 687 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>V9NA#aSW-5 zdpqZ%?_mc4)_m(mKM&OwFM+8O{5&*YJ~^>aLGj6gt?%c}&r#dI=DVeFT>BHhzC)+I z6xO_K5oej9&%~l|z~_L>dGe_Qo5rZu*_uxyyeeP3sM{&~TS)MEpIyD&uHBp~wwir- z-`pr0BJ@q;!Qv^OOd<|gD)BW}*KKun=zFEABv-uj^Qvze4u#xWf{KZ%nO5(mB%))p zbLIEE75ifIDXgVoJF+QuMyGbNry#c}1SB}{d1g7Tj0|8-i-wB(((@P=>URi+xD^-CL8%5P1X9TVDc zrFJS)*xg6V*_OWQN9Yq+pfvkUcY}&Xs3cQJ%H&$X2`o#soEy4wHt91>S`d=T5irsB zo2!G+lvO+k$E?*$cqpCeuXpol)uAw!n(E29f-x0|CGy)$8Fxqf2XNiElAOFt|7k1l z6sB9u#gPEYXWT23+P*yetDQq9|3k|ME6)l~xZCt;`G@`5f(hUH8CYg;&UieN dY-)3Ze_F@kM2q^BtiTk=;OXk;vd$@?2>>G39z*~D From 8f99e0494bb631c370ac6f7128689cb97974642d Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 16:39:27 -0300 Subject: [PATCH 44/85] Do not load 'scale' missing asset --- src/com/reco1l/osu/hud/GameplayHUD.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 1b4acad8e..893ff5e3f 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -203,7 +203,6 @@ class GameplayHUD : Container(), IGameplayEvents { if (value) { ResourceManager.getInstance().loadHighQualityAsset("delete", "delete.png") - ResourceManager.getInstance().loadHighQualityAsset("scale", "scale.png") ResourceManager.getInstance().loadHighQualityAsset("flip", "flip.png") elementSelector = HUDElementSelector(this) From d1a2f27e145a1d7f42b9c5ae35bc899c4595fa16 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 16:42:36 -0300 Subject: [PATCH 45/85] Continue editing after discarding changes --- src/com/reco1l/osu/hud/GameplayHUD.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 893ff5e3f..1f3ce1b8e 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -90,10 +90,9 @@ class GameplayHUD : Container(), IGameplayEvents { ToastLogger.showText("Changes saved!", true) } } - .addButton("Discard") { + .addButton("Reset & Cancel") { it.dismiss() updateThread { - setEditMode(false) setSkinData(OsuSkin.get().hudSkinData) ToastLogger.showText("Changes discarded!", true) } From 51f4bea4e9abfe705fe4189cb97d927824505cda Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 18:39:24 -0600 Subject: [PATCH 46/85] Force accuracy text size --- src/com/reco1l/andengine/text/TextureFont.kt | 2 +- .../reco1l/osu/hud/elements/HUDAccuracyCounter.kt | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/andengine/text/TextureFont.kt b/src/com/reco1l/andengine/text/TextureFont.kt index 2e9a40a2f..bf93865db 100644 --- a/src/com/reco1l/andengine/text/TextureFont.kt +++ b/src/com/reco1l/andengine/text/TextureFont.kt @@ -8,7 +8,7 @@ import org.anddev.andengine.opengl.util.GLHelper import javax.microedition.khronos.opengles.* import kotlin.math.* -open class TextureFont(private val characters: MutableMap) : Box() { +open class TextureFont(val characters: MutableMap) : Box() { override var autoSizeAxes = Axes.Both diff --git a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt index c9f7f7b33..0f84dfb03 100644 --- a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt @@ -1,6 +1,7 @@ package com.reco1l.osu.hud.elements import com.reco1l.osu.hud.HUDElement +import com.reco1l.osu.hud.HUDElementSkinData import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 @@ -20,7 +21,16 @@ class HUDAccuracyCounter : HUDElement() { onMeasureContentSize() } - override fun onGameplayUpdate(game: GameScene, statistics: StatisticV2, secondsElapsed: Float) { + override fun setSkinData(data: HUDElementSkinData?) { + super.setSkinData(data) + + // Some skins use a wider % character, so we need to adjust the width of the counter so it + // doesn't become too wide for the editor. + width = sprite.characters['0']!!.width * 7f + height = sprite.characters['0']!!.height.toFloat() + } + + override fun onGameplayUpdate(gameScene: GameScene, statistics: StatisticV2, secondsElapsed: Float) { sprite.text = format.format(statistics.accuracy) } From 81af86738cb91a551e1e01e258071d170316e63f Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 18:55:52 -0600 Subject: [PATCH 47/85] Ignore wrong entries while parsing HUDElements --- src/com/reco1l/osu/hud/HUDElement.kt | 2 +- src/com/reco1l/osu/hud/HUDSkinData.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 29f238594..1da6fd8f3 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -43,7 +43,7 @@ enum class HUDElements(val type: KClass) { companion object { operator fun get(type: KClass) = entries.first { it.type == type } - operator fun get(name: String) = valueOf(name) + operator fun get(name: String) = entries.find { it.name == name } } } diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 9312aa4e2..ed02dbedd 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -57,7 +57,7 @@ data class HUDSkinData(val elements: List) { val element = json.getJSONObject(i) HUDElementSkinData( - type = HUDElements.valueOf(element.getString("type")).type, + type = HUDElements[element.getString("type")]?.type ?: return@MutableList null, position = Vec2( element.optDouble("x", 0.0).toFloat(), element.optDouble("y", 0.0).toFloat(), @@ -69,8 +69,7 @@ data class HUDSkinData(val elements: List) { element.optDouble("scaleY", 1.0).toFloat() ) ) - } - + }.filterNotNull() ) From d3dac4c5922d4d5acb91e351b4b609dab5ea6db4 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 19:07:28 -0600 Subject: [PATCH 48/85] Add button to reset to default --- src/com/reco1l/osu/hud/GameplayHUD.kt | 12 +++++++++--- src/com/reco1l/osu/ui/Dialog.kt | 9 +++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 1f3ce1b8e..fcd7f65b0 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -80,8 +80,7 @@ class GameplayHUD : Container(), IGameplayEvents { MessageDialog() .setTitle("HUD Editor") - .setMessage("Do you want to save the changes?") - .addButton("Save") { + .addButton("Save changes") { it.dismiss() updateThread { ToastLogger.showText("Saving changes...", true) @@ -90,13 +89,20 @@ class GameplayHUD : Container(), IGameplayEvents { ToastLogger.showText("Changes saved!", true) } } - .addButton("Reset & Cancel") { + .addButton("Discard changes") { it.dismiss() updateThread { setSkinData(OsuSkin.get().hudSkinData) ToastLogger.showText("Changes discarded!", true) } } + .addButton("Reset to default") { + it.dismiss() + updateThread { + setSkinData(HUDSkinData.Default) + ToastLogger.showText("HUD set to default!", true) + } + } .addButton("Cancel") { it.dismiss() } .show() } diff --git a/src/com/reco1l/osu/ui/Dialog.kt b/src/com/reco1l/osu/ui/Dialog.kt index ae5be65f9..dc6c6ca52 100644 --- a/src/com/reco1l/osu/ui/Dialog.kt +++ b/src/com/reco1l/osu/ui/Dialog.kt @@ -11,6 +11,7 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY +import androidx.core.view.isVisible import com.edlplan.framework.easing.Easing import com.edlplan.ui.EasingHelper import com.edlplan.ui.fragment.BaseFragment @@ -72,10 +73,10 @@ open class MessageDialog : BaseFragment() { set(value) { field = value if (isCreated) { - findViewById(R.id.message)?.text = ( - if (isHTMLMessage) HtmlCompat.fromHtml(value.toString(), FROM_HTML_MODE_LEGACY) - else value - ) + findViewById(R.id.message)?.apply { + (parent as View).isVisible = value.isNotBlank() + text = if (isHTMLMessage) HtmlCompat.fromHtml(value.toString(), FROM_HTML_MODE_LEGACY) else value + } } } From 143c1d8223c06b200026bb5d29804bc2038e26d5 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 19:11:48 -0600 Subject: [PATCH 49/85] Reset selection when changing skin data --- src/com/reco1l/osu/hud/GameplayHUD.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index fcd7f65b0..392f4e939 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -35,9 +35,7 @@ class GameplayHUD : Container(), IGameplayEvents { set(value) { if (field != value) { field = value - mChildren?.fastForEach { - (it as? HUDElement)?.onSelectionStateChange(it == value) - } + forEachElement { it.onSelectionStateChange(it == value) } } } @@ -147,6 +145,7 @@ class GameplayHUD : Container(), IGameplayEvents { * Sets the skin data of the HUD. */ fun setSkinData(layoutData: HUDSkinData) { + selected = null mChildren?.filterIsInstance()?.forEach(IEntity::detachSelf) // First pass: We attach everything so that elements can reference between them when From b206c76e15e600274342437ee07fab4260764d8d Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 19:13:33 -0600 Subject: [PATCH 50/85] Fix element list becoming invisible --- .../osu/hud/editor/HUDElementSelector.kt | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index a32fc8c79..4440349b7 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -34,6 +34,32 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE private val elements = HUDElements.entries.map { it.type.primaryConstructor!!.call() } + private val elementList = ScrollableContainer().apply { + + scrollAxes = Axes.Y + relativeSizeAxes = Axes.Y + height = 1f + width = SELECTOR_WIDTH + indicatorY!!.width = 4f + + background = Box().apply { + color = ColorARGB(0xFF1E1E2E) + } + + attachChild(LinearContainer().apply { + relativeSizeAxes = Axes.X + width = 1f + padding = Vec4(16f) + spacing = 12f + orientation = Orientation.Vertical + + elements.forEach { element -> + attachChild(HUDElementPreview(element, hud)) + } + }) + + } + init { relativeSizeAxes = Axes.Y @@ -86,31 +112,7 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE }) - attachChild(ScrollableContainer().apply { - - scrollAxes = Axes.Y - relativeSizeAxes = Axes.Y - height = 1f - width = SELECTOR_WIDTH - indicatorY!!.width = 4f - - background = Box().apply { - color = ColorARGB(0xFF1E1E2E) - } - - attachChild(LinearContainer().apply { - relativeSizeAxes = Axes.X - width = 1f - padding = Vec4(16f) - spacing = 12f - orientation = Orientation.Vertical - - elements.forEach { element -> - attachChild(HUDElementPreview(element, hud)) - } - }) - - }) + attachChild(elementList) } @@ -120,7 +122,7 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE } clearEntityModifiers() - isVisible = true + elementList.isVisible = true moveToX(0f, 0.2f) hud.moveToX(SELECTOR_WIDTH, 0.2f) @@ -133,7 +135,7 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE } clearEntityModifiers() - moveToX(-SELECTOR_WIDTH, 0.2f).then { isVisible = false } + moveToX(-SELECTOR_WIDTH, 0.2f).then { elementList.isVisible = false } hud.moveToX(0f, 0.2f) hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) From 8bbbd05b7347e9fe145625761a66102bce9d4bac Mon Sep 17 00:00:00 2001 From: Reco1l Date: Mon, 24 Feb 2025 19:15:10 -0600 Subject: [PATCH 51/85] Fix writing error --- src/com/reco1l/osu/hud/HUDSkinData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index ed02dbedd..fc7bed3ee 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -39,7 +39,7 @@ data class HUDSkinData(val elements: List) { fun writeToJSON(data: HUDSkinData) = JSONArray().apply { data.elements.forEach { putObject { - put("type", it.type.simpleName) + put("type", HUDElements[it.type].name) put("x", it.position.x) put("y", it.position.y) put("anchor", Anchor.getName(it.anchor)) From dd047c8108ebb3864f329c69aafcd6a201041638 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 11:33:40 -0300 Subject: [PATCH 52/85] Account for scale while auto-applying closest anchor --- src/com/reco1l/osu/hud/HUDElement.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 1da6fd8f3..38d4f89cb 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -178,8 +178,8 @@ abstract class HUDElement : Container(), IGameplayEvents { private fun applyClosestAnchorOrigin() { - val drawSize = drawSize - val drawPosition = drawPosition + val drawSize = drawSize * scaleVec + val drawPosition = anchorOffset + position - drawSize * origin val parentDrawSize = (parent as ExtendedEntity).drawSize val relativeTopLeft = drawPosition / parentDrawSize @@ -245,9 +245,8 @@ abstract class HUDElement : Container(), IGameplayEvents { * Moves the element by the specified delta. */ fun move(deltaX: Float, deltaY: Float) { + setPosition(x + deltaX, y + deltaY) applyClosestAnchorOrigin() - x += deltaX - y += deltaY updateConnectionLine() } From 1307cb92fbae4af23c6a2776f8917062f1800438 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 11:34:32 -0300 Subject: [PATCH 53/85] Convert Anchor list to property --- src/com/reco1l/andengine/Anchor.kt | 24 +++++++++++++----------- src/com/reco1l/osu/hud/HUDElement.kt | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/com/reco1l/andengine/Anchor.kt b/src/com/reco1l/andengine/Anchor.kt index 9893c011f..b41aca000 100644 --- a/src/com/reco1l/andengine/Anchor.kt +++ b/src/com/reco1l/andengine/Anchor.kt @@ -32,17 +32,19 @@ object Anchor { val BottomRight = Vec2(1f, 1f) - fun getAll() = listOf( - TopLeft, - TopCenter, - TopRight, - CenterLeft, - Center, - CenterRight, - BottomLeft, - BottomCenter, - BottomRight - ) + val all by lazy { + listOf( + TopLeft, + TopCenter, + TopRight, + CenterLeft, + Center, + CenterRight, + BottomLeft, + BottomCenter, + BottomRight + ) + } fun getName(anchor: Vec2): String { return when (anchor) { diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 38d4f89cb..8e6acd873 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -187,7 +187,7 @@ abstract class HUDElement : Container(), IGameplayEvents { val relativeBottomRight = (drawPosition + drawSize) / parentDrawSize val relativeBottomLeft = (drawPosition + Vec2(0f, drawSize.y)) / parentDrawSize - val closest = Anchor.getAll().minBy { + val closest = Anchor.all.minBy { minOf( abs(relativeTopLeft.distance(it)), abs(relativeTopRight.distance(it)), From 8927a614fa99c2e7e78b13de55e063de346a0c75 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 11:38:40 -0300 Subject: [PATCH 54/85] Change text --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 2 +- 1 file changed, 1 insertion(+), 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 2ba8d4db9..1b976358c 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -1028,7 +1028,7 @@ public void start() { blockAreaFragment.show(false); if (isHUDEditorMode) { - ToastLogger.showText("Press back to exit HUD editor mode and save.", false); + ToastLogger.showText("Press back to show HUD editor menu.", false); } } From 4702820d10aa0a917cea44da2ed3affa119526cf Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 11:40:04 -0300 Subject: [PATCH 55/85] Fix PP not calculating during HUD editor --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 2 +- 1 file changed, 1 insertion(+), 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 1b976358c..d544eaff7 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -612,7 +612,7 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile, @Nul var sameParameters = lastDifficultyCalculationParameters != null && lastDifficultyCalculationParameters.equals(parameters); - if (!isHUDEditorMode && OsuSkin.get().getHUDSkinData().hasElement(HUDPPCounter.class)) { + if (isHUDEditorMode || OsuSkin.get().getHUDSkinData().hasElement(HUDPPCounter.class)) { // Calculate timed difficulty attributes switch (Config.getDifficultyAlgorithm()) { case droid -> { From ce04c34103ff458f9863588640da534b6055fac6 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 12:27:29 -0300 Subject: [PATCH 56/85] Clamp position to screen borders --- src/com/reco1l/osu/hud/HUDElement.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 8e6acd873..d4cf471d7 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -245,7 +245,20 @@ abstract class HUDElement : Container(), IGameplayEvents { * Moves the element by the specified delta. */ fun move(deltaX: Float, deltaY: Float) { - setPosition(x + deltaX, y + deltaY) + + val delta = Vec2(deltaX, deltaY) + val screenSpaceSize = drawSize * scaleVec + val deltaScreenSpacePosition = anchorOffset + position - screenSpaceSize * origin + delta + val parentDrawSize = (parent as ExtendedEntity).drawSize + + if (deltaScreenSpacePosition.x >= 0f && deltaScreenSpacePosition.x + screenSpaceSize.x <= parentDrawSize.x) { + x += deltaX + } + + if (deltaScreenSpacePosition.y >= 0f && deltaScreenSpacePosition.y + screenSpaceSize.y <= parentDrawSize.y) { + y += deltaY + } + applyClosestAnchorOrigin() updateConnectionLine() } From 9875358266fd7b5f75c8518533ec0accb3e039b7 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 14:26:53 -0300 Subject: [PATCH 57/85] Set default position to center --- src/com/reco1l/osu/hud/GameplayHUD.kt | 11 ++++++----- src/com/reco1l/osu/hud/HUDSkinData.kt | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 392f4e939..ce7490903 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -4,10 +4,7 @@ import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes import com.reco1l.andengine.container.Container import com.reco1l.osu.hud.editor.HUDElementSelector -import com.reco1l.osu.hud.elements.HUDAccuracyCounter -import com.reco1l.osu.hud.elements.HUDComboCounter -import com.reco1l.osu.hud.elements.HUDPieSongProgress -import com.reco1l.osu.hud.elements.HUDScoreCounter +import com.reco1l.osu.hud.elements.* import com.reco1l.osu.ui.MessageDialog import com.reco1l.osu.updateThread import com.reco1l.toolkt.kotlin.fastForEach @@ -166,6 +163,10 @@ class GameplayHUD : Container(), IGameplayEvents { // implemented, as it used cross-references between elements that are not possible to be // set in the editor. + val healthBar = getFirstOf() + healthBar?.anchor = Anchor.TopLeft + healthBar?.origin = Anchor.TopLeft + val scoreCounter = getFirstOf() scoreCounter?.anchor = Anchor.TopRight scoreCounter?.origin = Anchor.TopRight @@ -229,7 +230,7 @@ class GameplayHUD : Container(), IGameplayEvents { //region Gameplay Events - private fun forEachElement(action: (HUDElement) -> Unit) { + fun forEachElement(action: (HUDElement) -> Unit) { mChildren?.fastForEach { (it as? HUDElement)?.let(action) } diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index fc7bed3ee..8a1cc3b22 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -92,12 +92,12 @@ data class HUDElementSkinData( /** * The anchor of the element. */ - val anchor: Vec2 = Anchor.TopLeft, + val anchor: Vec2 = Anchor.Center, /** * The origin of the element. */ - val origin: Vec2 = Anchor.TopLeft, + val origin: Vec2 = Anchor.Center, /** * The scale applied to the element. From af1c3053dc6cb496e7bbe99a0a62a7b8dd2d613d Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 14:49:38 -0300 Subject: [PATCH 58/85] Introduce selection swapping for overlapping elements --- src/com/reco1l/osu/hud/GameplayHUD.kt | 2 +- src/com/reco1l/osu/hud/HUDElement.kt | 48 +++++++++++++++---- .../osu/hud/elements/HUDAccuracyCounter.kt | 9 ---- .../reco1l/osu/hud/elements/HUDHealthBar.kt | 16 ------- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index ce7490903..9278a5d2c 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -7,7 +7,7 @@ import com.reco1l.osu.hud.editor.HUDElementSelector import com.reco1l.osu.hud.elements.* import com.reco1l.osu.ui.MessageDialog import com.reco1l.osu.updateThread -import com.reco1l.toolkt.kotlin.fastForEach +import com.reco1l.toolkt.kotlin.* import org.anddev.andengine.engine.camera.hud.* import org.anddev.andengine.entity.IEntity import org.anddev.andengine.input.touch.TouchEvent diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index d4cf471d7..7e8041715 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -1,10 +1,12 @@ package com.reco1l.osu.hud +import android.view.* import com.reco1l.andengine.* import com.reco1l.andengine.container.Container import com.reco1l.andengine.shape.* import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 +import com.reco1l.osu.* import com.reco1l.osu.hud.editor.HUDElementOverlay import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDAverageOffsetCounter @@ -56,6 +58,12 @@ abstract class HUDElement : Container(), IGameplayEvents { val name: String get() = HUDElements[this::class].name.replace('_', ' ').capitalize() + /** + * Indicates whether the element is selected. + */ + val isSelected + get() = (parent as? GameplayHUD)?.selected == this + private var editorOverlay: HUDElementOverlay? = null @@ -115,12 +123,12 @@ abstract class HUDElement : Container(), IGameplayEvents { background?.alpha = if (isSelected) 0.5f else 0.15f editorOverlay?.isVisible = isSelected + connectionLine?.isVisible = isSelected if (isSelected) { updateConnectionLine() - connectionLine?.isVisible = true } else { - connectionLine?.isVisible = false + wasSelected = false } } @@ -131,27 +139,37 @@ abstract class HUDElement : Container(), IGameplayEvents { private var initialX = 0f private var initialY = 0f + private var wasSelected = false + private var wasMoved = false + + override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { if (isInEditMode) { + val hud = parent as GameplayHUD + val parentLocalX = drawX + localX val parentLocalY = drawY + localY - if (event.isActionDown) { - (parent as? GameplayHUD)?.selected = this + val deltaX = parentLocalX - initialX + val deltaY = parentLocalY - initialY + if (event.isActionDown) { initialX = parentLocalX initialY = parentLocalY + + wasMoved = false + wasSelected = hud.selected == this + + hud.selected = this return true } if (event.isActionMove) { - val deltaX = parentLocalX - initialX - val deltaY = parentLocalY - initialY - // Preventing from moving the element if it's not selected. - if ((parent as? GameplayHUD)?.selected == this) { + if (isSelected) { move(deltaX, deltaY) + wasMoved = true initialX = parentLocalX initialY = parentLocalY @@ -160,7 +178,19 @@ abstract class HUDElement : Container(), IGameplayEvents { } if (event.isActionUp) { - return (parent as? GameplayHUD)?.selected == this + if (wasSelected && isSelected && !wasMoved) { + hud.forEachElement { element -> + + if (element != this && element.contains(parentLocalX, parentLocalY)) { + hud.selected = element + + // Move to front so the next input event is handled by the selected element. + hud.setChildIndex(element, 0) + return@forEachElement + } + } + } + return true } } diff --git a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt index 0f84dfb03..31ec8b2b5 100644 --- a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt @@ -21,15 +21,6 @@ class HUDAccuracyCounter : HUDElement() { onMeasureContentSize() } - override fun setSkinData(data: HUDElementSkinData?) { - super.setSkinData(data) - - // Some skins use a wider % character, so we need to adjust the width of the counter so it - // doesn't become too wide for the editor. - width = sprite.characters['0']!!.width * 7f - height = sprite.characters['0']!!.height.toFloat() - } - override fun onGameplayUpdate(gameScene: GameScene, statistics: StatisticV2, secondsElapsed: Float) { sprite.text = format.format(statistics.accuracy) } diff --git a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt index 690662e24..c6a05e8db 100644 --- a/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt +++ b/src/com/reco1l/osu/hud/elements/HUDHealthBar.kt @@ -91,22 +91,6 @@ class HUDHealthBar : HUDElement() { fillClear.setPosition(fill.x + fill.drawWidth, fill.y) onMeasureContentSize() - - - } - - - override fun onAttached() { - - if (parent !is HUDElementPreview) { - // Some skins use the background texture as a playfield background/border with a fullscreen - // size texture, this will make the health bar take the entire screen in the HUD editor. We - // clamp the size to the fill size to avoid this. - width = width.coerceAtMost(fill.drawX + fill.width) - height = height.coerceAtMost(fill.drawY + fill.height) - } - - super.onAttached() } From 1ba15df8ed8051764d0e69d14cca09955123d416 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 14:51:06 -0300 Subject: [PATCH 59/85] Remove element clamping --- src/com/reco1l/osu/hud/HUDElement.kt | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 7e8041715..7a1b1ff5c 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -275,20 +275,7 @@ abstract class HUDElement : Container(), IGameplayEvents { * Moves the element by the specified delta. */ fun move(deltaX: Float, deltaY: Float) { - - val delta = Vec2(deltaX, deltaY) - val screenSpaceSize = drawSize * scaleVec - val deltaScreenSpacePosition = anchorOffset + position - screenSpaceSize * origin + delta - val parentDrawSize = (parent as ExtendedEntity).drawSize - - if (deltaScreenSpacePosition.x >= 0f && deltaScreenSpacePosition.x + screenSpaceSize.x <= parentDrawSize.x) { - x += deltaX - } - - if (deltaScreenSpacePosition.y >= 0f && deltaScreenSpacePosition.y + screenSpaceSize.y <= parentDrawSize.y) { - y += deltaY - } - + setPosition(x + deltaX, y + deltaY) applyClosestAnchorOrigin() updateConnectionLine() } From e6e16bb4339024c1e9e6a3887bd564da7d6c4200 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 15:00:14 -0300 Subject: [PATCH 60/85] Add animations --- src/com/reco1l/osu/hud/HUDElement.kt | 13 +++++++++---- src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt | 12 ++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 7a1b1ff5c..97de67eea 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -121,9 +121,14 @@ abstract class HUDElement : Container(), IGameplayEvents { open fun onSelectionStateChange(isSelected: Boolean) { - background?.alpha = if (isSelected) 0.5f else 0.15f - editorOverlay?.isVisible = isSelected - connectionLine?.isVisible = isSelected + background?.clearEntityModifiers() + background?.fadeTo(if (isSelected) 0.5f else 0.15f, 0.1f) + + editorOverlay?.clearEntityModifiers() + editorOverlay?.fadeTo(if (isSelected) 1f else 0f, 0.1f) + + connectionLine?.clearEntityModifiers() + connectionLine?.fadeTo(if (isSelected) 1f else 0f, 0.1f) if (isSelected) { updateConnectionLine() @@ -247,7 +252,7 @@ abstract class HUDElement : Container(), IGameplayEvents { connectionLine = Line().apply { color = ColorARGB(0xFFF27272) lineWidth = 10f - isVisible = false + alpha = 0f } parent!!.attachChild(connectionLine!!) } diff --git a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt index 86d5ba76f..b3cf69be7 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt @@ -60,7 +60,7 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() init { - isVisible = false + alpha = 0f setSize(Config.getRES_WIDTH().toFloat(), Config.getRES_HEIGHT().toFloat()) attachChild(outline) @@ -87,7 +87,7 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { - if (isVisible) { + if (alpha == 1f) { return super.onAreaTouched(event, localX, localY) } return false @@ -181,13 +181,13 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() init { setSize(BUTTON_SIZE, BUTTON_SIZE) - background = RoundedBox().apply { + attachChild(RoundedBox().apply { cornerRadius = 12f color = back - } - + relativeSizeAxes = Axes.Both + setSize(1f, 1f) + }) attachChild(icon) - } override fun onAreaTouched(event: TouchEvent, localX: Float, localY: Float): Boolean { From 1bf7740b21aad4434d652316cb63d057c5a54909 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 15:03:20 -0300 Subject: [PATCH 61/85] Save JSON data after saving --- src/com/reco1l/osu/hud/GameplayHUD.kt | 1 + src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 9278a5d2c..d405f70f5 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -124,6 +124,7 @@ class GameplayHUD : Container(), IGameplayEvents { json.put("HUD", HUDSkinData.writeToJSON(data)) jsonFile.writeText(json.toString()) + SkinJsonReader.getReader().currentData = json OsuSkin.get().hudSkinData = data } diff --git a/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java b/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java index 6a52bf8fa..656cf0bab 100644 --- a/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java +++ b/src/ru/nsu/ccfit/zuev/skins/SkinJsonReader.java @@ -185,5 +185,9 @@ public void loadArray(String tag, @NonNull JSONObject data, Consumer<@Nullable J public JSONObject getCurrentData() { return currentData; } + + public JSONObject setCurrentData(JSONObject currentData) { + return this.currentData = currentData; + } } From e4385bb42cb36f2303f2f63a4e17a125be0afdb7 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 15:05:28 -0300 Subject: [PATCH 62/85] Fix previews being centered in the selector --- src/com/reco1l/osu/hud/editor/HUDElementPreview.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt index 71b01af41..ad87fbd10 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt @@ -43,7 +43,13 @@ class HUDElementPreview(private val element: HUDElement, val hud: GameplayHUD): attachChild(element) attachChild(label) - element.setSkinData(HUDElementSkinData(element::class)) + element.setSkinData( + HUDElementSkinData( + type = element::class, + anchor = Anchor.TopLeft, + origin = Anchor.TopLeft, + ) + ) } override fun onManagedDraw(gl: GL10, camera: Camera) { From d29b7b5e83574338342e106af317afa5ebae4e30 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Tue, 25 Feb 2025 15:42:19 -0300 Subject: [PATCH 63/85] Fix music not resetting in some scenarios --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java index d544eaff7..39662579d 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -1442,7 +1442,7 @@ public void onUpdate(final float pSecondsElapsed) { elapsedTime = initialElapsedTime; loadGame(lastBeatmapInfo, null, null); stat.reset(); - skip(); + skip(true); return; } @@ -1614,19 +1614,26 @@ private void updatePassiveObjects(float deltaTime) { } } - public void skip() + public void skip() { + skip(false); + } + + public void skip(boolean force) { RoomScene.INSTANCE.getChat().dismiss(); - if (elapsedTime > skipTime - 1f) + if (elapsedTime > skipTime - 1f && !force) { return; + } - if (GlobalManager.getInstance().getSongService().getStatus() != Status.PLAYING) { - GlobalManager.getInstance().getSongService().play(); - GlobalManager.getInstance().getSongService().setVolume(Config.getBgmVolume()); - totalLength = GlobalManager.getInstance().getSongService().getLength(); + SongService songService = GlobalManager.getInstance().getSongService(); + if (songService.getStatus() != Status.PLAYING) { + songService.play(); + songService.setVolume(Config.getBgmVolume()); + totalLength = songService.getLength(); musicStarted = true; } + ResourceManager.getInstance().getSound("menuhit").play(); float difference = skipTime - elapsedTime; @@ -1638,7 +1645,11 @@ public void skip() updatePassiveObjects(difference); - GlobalManager.getInstance().getSongService().seekTo(seekTime); + songService.seekTo(seekTime); + if (songService.getStatus() != Status.PLAYING) { + songService.play(); + } + if (video != null) { video.seekTo(videoSeekTime); } @@ -2671,7 +2682,7 @@ private void calculateAllSliderPaths(final CoroutineScope scope) { for (var obj : playableBeatmap.getHitObjects().objects) { JobKt.ensureActive(scope.getCoroutineContext()); - if (!(obj instanceof com.rian.osu.beatmap.hitobject.Slider slider)) { + if (!(obj instanceof Slider slider)) { continue; } From 0db82cdadfaf491ca0cee4e6eefdad53584b32e4 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:03:26 +0700 Subject: [PATCH 64/85] Remove unused imports --- src/com/reco1l/osu/hud/HUDElement.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 97de67eea..a697ab186 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -1,12 +1,10 @@ package com.reco1l.osu.hud -import android.view.* import com.reco1l.andengine.* import com.reco1l.andengine.container.Container import com.reco1l.andengine.shape.* import com.reco1l.framework.ColorARGB import com.reco1l.framework.math.Vec2 -import com.reco1l.osu.* import com.reco1l.osu.hud.editor.HUDElementOverlay import com.reco1l.osu.hud.elements.HUDAccuracyCounter import com.reco1l.osu.hud.elements.HUDAverageOffsetCounter From a7de365b25e39b90c9c116146e6d44f645b90f06 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:13:41 +0700 Subject: [PATCH 65/85] Remove another unused import --- src/com/reco1l/framework/math/Vec2.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/reco1l/framework/math/Vec2.kt b/src/com/reco1l/framework/math/Vec2.kt index 36aeecc60..9188dd633 100644 --- a/src/com/reco1l/framework/math/Vec2.kt +++ b/src/com/reco1l/framework/math/Vec2.kt @@ -1,6 +1,5 @@ package com.reco1l.framework.math -import com.reco1l.toolkt.toDegrees import kotlin.math.* data class Vec2(val x: Float, val y: Float) { From c3b1852022d891b8246c7697a849f12252a16a48 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 15:47:01 -0300 Subject: [PATCH 66/85] Fix wrong alpha when element was never selected --- src/com/reco1l/osu/hud/HUDElement.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index a697ab186..ca2091e06 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -102,7 +102,7 @@ abstract class HUDElement : Container(), IGameplayEvents { if (value) { background = Box().apply { color = ColorARGB(0x29F27272) - alpha = 0.25f + alpha = 0.15f } editorOverlay = HUDElementOverlay(this) From 8bae314b3a2e3f0a841cac35419b4da0475a1516 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 15:49:27 -0300 Subject: [PATCH 67/85] Dispose selection when expanding HUDElementSelector --- src/com/reco1l/osu/hud/editor/HUDElementSelector.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index 4440349b7..c3fcc2c75 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -120,6 +120,8 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE if (isExpanded) { return } + + hud.selected = null clearEntityModifiers() elementList.isVisible = true From 17884c556098503ed5c74d4f61578e943c3d12b9 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 15:50:17 -0300 Subject: [PATCH 68/85] Do not move HUD when expanding HUDElement selector --- src/com/reco1l/osu/hud/editor/HUDElementSelector.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt index c3fcc2c75..64c5c9a51 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementSelector.kt @@ -122,13 +122,10 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE } hud.selected = null - clearEntityModifiers() + clearEntityModifiers() elementList.isVisible = true moveToX(0f, 0.2f) - - hud.moveToX(SELECTOR_WIDTH, 0.2f) - hud.sizeToX(Config.getRES_WIDTH() - SELECTOR_WIDTH, 0.2f) } fun collapse() { @@ -136,11 +133,7 @@ class HUDElementSelector(private val hud: GameplayHUD) : Container(), IGameplayE return } clearEntityModifiers() - moveToX(-SELECTOR_WIDTH, 0.2f).then { elementList.isVisible = false } - - hud.moveToX(0f, 0.2f) - hud.sizeToX(Config.getRES_WIDTH().toFloat(), 0.2f) } From a561c37368a13f1dcf513e18ea0da90f9bddc25f Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 15:51:24 -0300 Subject: [PATCH 69/85] Collapse element selector when adding an element --- src/com/reco1l/osu/hud/GameplayHUD.kt | 8 ++++++-- src/com/reco1l/osu/hud/editor/HUDElementPreview.kt | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index d405f70f5..1320500a0 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -36,10 +36,14 @@ class GameplayHUD : Container(), IGameplayEvents { } } + /** + * The element selector used in edit mode. + */ + var elementSelector: HUDElementSelector? = null + private set - private var isInEditMode = false - private var elementSelector: HUDElementSelector? = null + private var isInEditMode = false init { diff --git a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt index ad87fbd10..d7f40bb89 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementPreview.kt @@ -85,6 +85,7 @@ class HUDElementPreview(private val element: HUDElement, val hud: GameplayHUD): scaleTo(0.9f, 0.1f).scaleTo(1f, 0.1f) hud.addElement(HUDElementSkinData(element::class)) + hud.elementSelector?.collapse() } } From c91a4f7084b2a942064385b11d601c532ded41e7 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 20:50:35 -0300 Subject: [PATCH 70/85] Fix connection line not appearing when selecting an element for the first time --- src/com/reco1l/osu/hud/HUDElement.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index ca2091e06..c86b688dd 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -125,14 +125,14 @@ abstract class HUDElement : Container(), IGameplayEvents { editorOverlay?.clearEntityModifiers() editorOverlay?.fadeTo(if (isSelected) 1f else 0f, 0.1f) - connectionLine?.clearEntityModifiers() - connectionLine?.fadeTo(if (isSelected) 1f else 0f, 0.1f) - if (isSelected) { updateConnectionLine() } else { wasSelected = false } + + connectionLine?.clearEntityModifiers() + connectionLine?.fadeTo(if (isSelected) 1f else 0f, 0.1f) } //endregion From 4e91b64306bf49fac03349bae43a638a498e8a67 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 21:55:24 -0300 Subject: [PATCH 71/85] Propagate input from last child to first child (front to back) --- src/com/reco1l/andengine/ExtendedEntity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/reco1l/andengine/ExtendedEntity.kt b/src/com/reco1l/andengine/ExtendedEntity.kt index 7a2a90aa6..f7f92ec93 100644 --- a/src/com/reco1l/andengine/ExtendedEntity.kt +++ b/src/com/reco1l/andengine/ExtendedEntity.kt @@ -870,7 +870,8 @@ abstract class ExtendedEntity( } try { - mChildren?.fastForEach { child -> + for (i in childCount - 1 downTo 0) { + val child = getChild(i) if (child is ExtendedEntity && child.contains(localX, localY)) { if (child.onAreaTouched(event, localX - child.drawX, localY - child.drawY)) { inputBindings[event.pointerID] = child From 4c287f6d27e55d92a832dab35a5e9ea20c77c9ac Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 21:56:09 -0300 Subject: [PATCH 72/85] Move elements to front properly when selecting --- src/com/reco1l/osu/hud/GameplayHUD.kt | 3 +++ src/com/reco1l/osu/hud/HUDElement.kt | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 1320500a0..7e1d07f6d 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -33,6 +33,9 @@ class GameplayHUD : Container(), IGameplayEvents { if (field != value) { field = value forEachElement { it.onSelectionStateChange(it == value) } + + // Move to front + setChildIndex(value, mChildren.size - 1) } } diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index c86b688dd..3d6bcd173 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -186,9 +186,6 @@ abstract class HUDElement : Container(), IGameplayEvents { if (element != this && element.contains(parentLocalX, parentLocalY)) { hud.selected = element - - // Move to front so the next input event is handled by the selected element. - hud.setChildIndex(element, 0) return@forEachElement } } From fcc266ff6675281f4696ccf020a8055ad57fc3ff Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 21:57:16 -0300 Subject: [PATCH 73/85] Fallback to origin center while parsing --- src/com/reco1l/osu/hud/HUDSkinData.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 8a1cc3b22..95cfa42c9 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -62,8 +62,8 @@ data class HUDSkinData(val elements: List) { element.optDouble("x", 0.0).toFloat(), element.optDouble("y", 0.0).toFloat(), ), - anchor = Anchor.getFromName(element.optString("anchor", "TopLeft")), - origin = Anchor.getFromName(element.optString("origin", "TopLeft")), + anchor = Anchor.getFromName(element.optString("anchor", "Center")), + origin = Anchor.getFromName(element.optString("origin", "Center")), scale = Vec2( element.optDouble("scaleX", 1.0).toFloat(), element.optDouble("scaleY", 1.0).toFloat() From b278c173b84dcafdc07b8c9062e86c0dbc3626a4 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 21:57:42 -0300 Subject: [PATCH 74/85] Simplify default layout configuration --- src/com/reco1l/osu/hud/GameplayHUD.kt | 39 +++++---------------------- src/com/reco1l/osu/hud/HUDSkinData.kt | 36 +++++++++++++++++++++---- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 7e1d07f6d..414305010 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -170,41 +170,14 @@ class GameplayHUD : Container(), IGameplayEvents { // The default layout is hardcoded to keep the original layout before the HUD editor was // implemented, as it used cross-references between elements that are not possible to be // set in the editor. + val scoreCounter = getFirstOf()!! + val accuracyCounter = getFirstOf()!! + val pieSongProgress = getFirstOf()!! - val healthBar = getFirstOf() - healthBar?.anchor = Anchor.TopLeft - healthBar?.origin = Anchor.TopLeft + accuracyCounter.y += scoreCounter.y + scoreCounter.drawHeight - val scoreCounter = getFirstOf() - scoreCounter?.anchor = Anchor.TopRight - scoreCounter?.origin = Anchor.TopRight - scoreCounter?.setScale(0.96f) - scoreCounter?.x = -10f - - val accuracyCounter = getFirstOf() - accuracyCounter?.anchor = Anchor.TopRight - accuracyCounter?.origin = Anchor.TopRight - accuracyCounter?.setScale(0.6f * 0.96f) - accuracyCounter?.setPosition(-17f, 9f) - - if (scoreCounter != null && accuracyCounter != null) { - accuracyCounter.y += scoreCounter.y + scoreCounter.drawHeight - } - - val pieSongProgress = getFirstOf() - pieSongProgress?.anchor = Anchor.TopRight - pieSongProgress?.origin = Anchor.CenterRight - - if (pieSongProgress != null && accuracyCounter != null) { - pieSongProgress.y = accuracyCounter.y + accuracyCounter.heightScaled / 2f - pieSongProgress.x = accuracyCounter.x - accuracyCounter.widthScaled - 18f - } - - val comboCounter = getFirstOf() - comboCounter?.anchor = Anchor.BottomLeft - comboCounter?.origin = Anchor.BottomLeft - comboCounter?.setPosition(10f, -10f) - comboCounter?.setScale(1.28f) + pieSongProgress.y = accuracyCounter.y + accuracyCounter.heightScaled / 2f + pieSongProgress.x = accuracyCounter.x - accuracyCounter.widthScaled - 18f } //endregion diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 95cfa42c9..6ea674bb0 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -26,11 +26,37 @@ data class HUDSkinData(val elements: List) { @JvmField val Default = HUDSkinData( listOf( - HUDElementSkinData(type = HUDAccuracyCounter::class), - HUDElementSkinData(type = HUDComboCounter::class), - HUDElementSkinData(type = HUDPieSongProgress::class), - HUDElementSkinData(type = HUDHealthBar::class), - HUDElementSkinData(type = HUDScoreCounter::class) + HUDElementSkinData( + type = HUDAccuracyCounter::class, + anchor = Anchor.TopRight, + origin = Anchor.TopRight, + scale = Vec2(0.6f * 0.96f), + position = Vec2(-17f, 9f) + ), + HUDElementSkinData( + type = HUDComboCounter::class, + anchor = Anchor.BottomLeft, + origin = Anchor.BottomLeft, + position = Vec2(10f, -10f), + scale = Vec2(1.28f) + ), + HUDElementSkinData( + type = HUDPieSongProgress::class, + anchor = Anchor.TopRight, + origin = Anchor.CenterRight + ), + HUDElementSkinData( + type = HUDHealthBar::class, + anchor = Anchor.TopLeft, + origin = Anchor.TopLeft + ), + HUDElementSkinData( + type = HUDScoreCounter::class, + anchor = Anchor.TopRight, + origin = Anchor.TopRight, + scale = Vec2(0.96f), + position = Vec2(-10f, 0f) + ) ) ) From 7fabd5fdb15493083076b3baa2dcf760ec99bd8f Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 23:19:32 -0300 Subject: [PATCH 75/85] Remove flipping and base scale center into the origin --- assets/flip.png | Bin 1303 -> 0 bytes src/com/reco1l/andengine/Entities.kt | 2 +- src/com/reco1l/osu/hud/GameplayHUD.kt | 5 ++- src/com/reco1l/osu/hud/HUDElement.kt | 14 +++--- .../osu/hud/editor/HUDElementOverlay.kt | 41 +++++++++--------- 5 files changed, 30 insertions(+), 32 deletions(-) delete mode 100644 assets/flip.png diff --git a/assets/flip.png b/assets/flip.png deleted file mode 100644 index 5766712eec07ea5855dcc92c19fe5fe09bdc8127..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1303 zcmah{dpr|(0RQcQSyr2yM-P2)Zp)3hkTHv9n%8ji%qyWf)Y^w)5iO~Yqon&NBad=6 z%Xw96$F!O{@)*jS-Su%8)~+Pa$J$sY@z~6XK4+pR#-)GW(DCABZ&+2I=bCfZqYQ@xx7Ds{PUo28E<9+zh*1}` z>xEcsdbdr^@ti1|#gbA;?#$8+#yL8bXIdP5H+qO>D1kTNH&x%&*mbwzU*26G79QJ? zsy!aplwnrkkswege_~JDudnmIAYJ5@BqLOsec9XdBZw_wILxJ|V;gW7e5KP~a|i$z zZ;cAe7t^Cp{T5xnp>L1`GsUwRPLATq0aOG`-^$9s^S1=kKyyD_AWpEFR)yEH+MLUXDV?@ZcmKi{2iDkkkj z)ia>1rJ{aOH*|Cz@2AdvmaPr^$gSj4D+t*+kp3dh8~O)(0O1I3j75D zt-$#9BUvMmV&DCBW4crU7-V2mw2k;S$6TPfyjuyMgB7;Rm zO}6j(z4`q&I|fB*ntm%<2b!qX{ls8--DGgjQOe>1C){)j?pLm5NDvG7p30>;ZG!>dmS_X5|9f!1V(FUfsR4GC zbOL;Yzh1sViS)9i!#H>#|CWV-tET~y7(*C7@(;&|Ui!SO%qy#eIa`%V-XK5)?`Plp zmdNU3i-+ZkSGPj4$RD=8xW$k&p&Yb}ES`{u)`> zzD&{%pMelD3D|9FzzjWz zHv_AJ+h6gq0Hh85i9VGtpEvZWd8n7X)A!<~ndZlpbWy?eI6@5H^25QjR5@dM>c-oM zJ^D0T--)4T{Qb$exs$QX6g;8udD*^a8m-=8Tgj@wQD#&6Q?k{r)$OBIjynYO2klB# zlZa^uUX8UvqPCV@(l^{oL*{kZ5hC7qPc?Vz< zDJm8f%!bYfrKwNw1}nAPS6uMO1sL~RhFXGb9M8hf7c!Fk*lD*b&R-lVfk!RGiW^$| zqh|4(X&Xj2Gg)`-a5wfvV$8BGNMWV#(*i~25CCF>jFT3&!1t+IrOLpPT6H$?aPy(o IQKB;c1-c4JjsO4v diff --git a/src/com/reco1l/andengine/Entities.kt b/src/com/reco1l/andengine/Entities.kt index fc6e413d6..86e3b43ae 100644 --- a/src/com/reco1l/andengine/Entities.kt +++ b/src/com/reco1l/andengine/Entities.kt @@ -64,7 +64,7 @@ val ExtendedEntity.drawPosition /** * The scale of the entity. */ -var ExtendedEntity.scaleVec +var ExtendedEntity.scale get() = Vec2(scaleX, scaleY) set(value) { setScale(value.x, value.y) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 414305010..3042bd559 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -35,7 +35,9 @@ class GameplayHUD : Container(), IGameplayEvents { forEachElement { it.onSelectionStateChange(it == value) } // Move to front - setChildIndex(value, mChildren.size - 1) + if (value != null) { + setChildIndex(value, mChildren.size - 1) + } } } @@ -189,7 +191,6 @@ class GameplayHUD : Container(), IGameplayEvents { if (value) { ResourceManager.getInstance().loadHighQualityAsset("delete", "delete.png") - ResourceManager.getInstance().loadHighQualityAsset("flip", "flip.png") elementSelector = HUDElementSelector(this) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index 3d6bcd173..ab0718680 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -76,10 +76,7 @@ abstract class HUDElement : Container(), IGameplayEvents { if (data != null) { anchor = data.anchor origin = data.origin - - setScaleCenter(0.5f, 0.5f) setScale(data.scale.x, data.scale.y) - setPosition(data.position.x, data.position.y) } } @@ -88,7 +85,7 @@ abstract class HUDElement : Container(), IGameplayEvents { type = this::class, anchor = anchor, origin = origin, - scale = scaleVec, + scale = scale, position = Vec2(x, y) ) @@ -208,7 +205,7 @@ abstract class HUDElement : Container(), IGameplayEvents { private fun applyClosestAnchorOrigin() { - val drawSize = drawSize * scaleVec + val drawSize = drawSize * scale val drawPosition = anchorOffset + position - drawSize * origin val parentDrawSize = (parent as ExtendedEntity).drawSize @@ -233,12 +230,11 @@ abstract class HUDElement : Container(), IGameplayEvents { } if (origin != closest) { - val previousOriginOffset = originOffset + val previousOriginOffset = -(size * scale * origin) + val originOffset = -(size * scale * closest) origin = closest position -= originOffset - previousOriginOffset } - - setScaleCenter(0.5f, 0.5f) } private fun updateConnectionLine() { @@ -253,7 +249,7 @@ abstract class HUDElement : Container(), IGameplayEvents { } connectionLine!!.fromPoint = anchorOffset - connectionLine!!.toPoint = (editorOverlay?.outline?.drawPosition ?: Vec2.Zero) + drawSize * scaleVec.absolute() * origin + connectionLine!!.toPoint = (editorOverlay?.outline?.drawPosition ?: Vec2.Zero) + drawSize * scale.absolute() * origin } override fun setScaleX(pScaleX: Float) { diff --git a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt index b3cf69be7..0740d59e9 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt @@ -1,7 +1,7 @@ package com.reco1l.osu.hud.editor -import com.reco1l.andengine.Anchor -import com.reco1l.andengine.Axes +import android.util.* +import com.reco1l.andengine.* import com.reco1l.andengine.container.ConstraintContainer import com.reco1l.andengine.container.Container import com.reco1l.andengine.container.LinearContainer @@ -12,6 +12,8 @@ import com.reco1l.andengine.text.* import com.reco1l.framework.ColorARGB import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.updateThread +import com.reco1l.toolkt.kotlin.* +import com.rian.osu.math.* import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager @@ -31,6 +33,7 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() origin = Anchor.BottomCenter orientation = Orientation.Horizontal spacing = 4f + y = -10f attachChild(Button("delete", ColorARGB(0xFF260000)) { updateThread { @@ -38,16 +41,6 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } }) - // Flip horizontally - attachChild(Button("flip", ColorARGB(0xFF181825)) { - element.scaleX = -element.scaleX - }) - - // Flip vertically - attachChild(Button("flip", ColorARGB(0xFF181825)) { - element.scaleY = -element.scaleY - }.apply { icon.rotation = -90f }) - } private val nameText = ExtendedText().apply { @@ -96,11 +89,18 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() override fun onManagedUpdate(pSecondsElapsed: Float) { // We need to cancel scale center - outline.x = element.drawX + (element.drawWidth * element.scaleCenterX) * (1f - abs(element.scaleX)) - outline.y = element.drawY + (element.drawHeight * element.scaleCenterY) * (1f - abs(element.scaleY)) + outline.x = element.anchorOffsetX + element.x - (element.widthScaled * element.origin.x) + outline.y = element.anchorOffsetY + element.y - (element.heightScaled * element.origin.y) - outline.width = element.drawWidth * abs(element.scaleX) - outline.height = element.drawHeight * abs(element.scaleY) + outline.width = element.widthScaled + outline.height = element.heightScaled + + // Show only the tips that are not at the origin. + mChildren?.fastForEach { + if (it is Tip) { + it.isVisible = it.anchor.x != element.origin.x && it.anchor.y != element.origin.y + } + } super.onManagedUpdate(pSecondsElapsed) } @@ -151,11 +151,11 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() deltaY = -deltaY } - val deltaScaleX = deltaX / 100f - val deltaScaleY = deltaY / 100f + val deltaScaleX = deltaX / element.widthScaled + val deltaScaleY = deltaY / element.heightScaled - element.scaleX = (abs(element.scaleX) + deltaScaleX).coerceIn(0.5f, 5f).withSign(element.scaleX) - element.scaleY = (abs(element.scaleY) + deltaScaleY).coerceIn(0.5f, 5f).withSign(element.scaleY) + element.scaleX = (element.scaleX + deltaScaleX).coerceIn(0.5f, 5f) + element.scaleY = (element.scaleY + deltaScaleY).coerceIn(0.5f, 5f) return true } @@ -180,6 +180,7 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() init { setSize(BUTTON_SIZE, BUTTON_SIZE) + scaleCenter = Anchor.Center attachChild(RoundedBox().apply { cornerRadius = 12f From 8e6a87673356a1e123abce4285f035b2ff5679a1 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 23:27:23 -0300 Subject: [PATCH 76/85] Collapse element selector when an element is selected --- src/com/reco1l/osu/hud/GameplayHUD.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 3042bd559..cfe59acee 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -37,6 +37,7 @@ class GameplayHUD : Container(), IGameplayEvents { // Move to front if (value != null) { setChildIndex(value, mChildren.size - 1) + elementSelector?.collapse() } } } From a5df5e566e568b65c1ae17270e71611506daabb9 Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 23:37:46 -0300 Subject: [PATCH 77/85] Add button to reset element aspect ratio --- src/com/reco1l/osu/hud/GameplayHUD.kt | 1 + src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index cfe59acee..3e70bb8f6 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -192,6 +192,7 @@ class GameplayHUD : Container(), IGameplayEvents { if (value) { ResourceManager.getInstance().loadHighQualityAsset("delete", "delete.png") + ResourceManager.getInstance().loadHighQualityAsset("oneone", "one-one.png") elementSelector = HUDElementSelector(this) diff --git a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt index 0740d59e9..b233336ce 100644 --- a/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt +++ b/src/com/reco1l/osu/hud/editor/HUDElementOverlay.kt @@ -1,6 +1,5 @@ package com.reco1l.osu.hud.editor -import android.util.* import com.reco1l.andengine.* import com.reco1l.andengine.container.ConstraintContainer import com.reco1l.andengine.container.Container @@ -13,11 +12,9 @@ import com.reco1l.framework.ColorARGB import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.updateThread import com.reco1l.toolkt.kotlin.* -import com.rian.osu.math.* import org.anddev.andengine.input.touch.TouchEvent import ru.nsu.ccfit.zuev.osu.Config import ru.nsu.ccfit.zuev.osu.ResourceManager -import kotlin.math.* class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() { @@ -41,6 +38,10 @@ class HUDElementOverlay(private val element: HUDElement) : ConstraintContainer() } }) + attachChild(Button("oneone", ColorARGB(0xFF002626)) { + element.setScale((element.scaleX + element.scaleY) / 2f) + }) + } private val nameText = ExtendedText().apply { From 24a3b5ac1b2c9cb2b02b0d417b800044cf80005b Mon Sep 17 00:00:00 2001 From: Reco1l Date: Fri, 28 Feb 2025 23:37:58 -0300 Subject: [PATCH 78/85] Add button to reset element aspect ratio --- assets/one-one.png | Bin 0 -> 853 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/one-one.png diff --git a/assets/one-one.png b/assets/one-one.png new file mode 100644 index 0000000000000000000000000000000000000000..086c5e2235f3153cd61c0a25ee33cc7f1630f24f GIT binary patch literal 853 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>VCMI9aSW-5 zdpkQf?2&;)>-{OBXB;Q~yFB-S*OaLXv?XItmZ%@L7il-vZm3Y|llRGXzExm5=lJ{? z&z5~ZS$nzdL67}PX1`T^I~W~oIDuwwWKOCSq6JlOhC%GK{q=KiPMz_6_5O1By*Kx+ zHLudCKT}+}v7&00$i5w)(yseH=9vHcYr?EznbKP~g*H^g+_gyGY!O^L_4PCH?)}g6 zHvE(|=QGZ)i|X@xmvp;4cRKHZ!$qQbzii4H?%mF6UkHyz#I5Ttn;y*ogJu^$gzSjZH^$b4}csMOCtf`Kjqv@D7YyL{62er$x=QTyN z+~;6uSiZo&@XRNrZ&xoJ3l4Y`eBpQWw)y2ZFR?i=HSFTub|p83L4{$({TT1t4$Kbu z3^{gky!zj2r!qP)F?e0rcR6Qyt-OBH?(TwD*E>`c6d7fI*6)&GV0ck%cl7I3ch0Wh ztbA7{4~O46FL$$YCYXOY%jhMby5c?4<$Lm~9YQXguc~xf-@f?fb@|4kY|bS870o{% zURuX+qwgKpgQsiKK4x#9eM9*bZ+mX=_QkLJE~n$-Qo`=`MLE8*mR=5LWrS--8? z9>2`ptSHl?%Qkza`D}~LS&z-W|NdjPVEx+{{Hld%+y2&BmS*pMJMZ;BeQ$?T3H=|X n7&=xmF)$>tANiz0JL>$=n`aBW4mAG+rey|CS3j3^P6 Date: Sat, 1 Mar 2025 09:56:58 +0700 Subject: [PATCH 79/85] Remove unused import --- src/com/reco1l/osu/hud/GameplayHUD.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/GameplayHUD.kt b/src/com/reco1l/osu/hud/GameplayHUD.kt index 3e70bb8f6..664502e0a 100644 --- a/src/com/reco1l/osu/hud/GameplayHUD.kt +++ b/src/com/reco1l/osu/hud/GameplayHUD.kt @@ -1,6 +1,5 @@ package com.reco1l.osu.hud -import com.reco1l.andengine.Anchor import com.reco1l.andengine.Axes import com.reco1l.andengine.container.Container import com.reco1l.osu.hud.editor.HUDElementSelector From 298b35b8ad26924bab8c4e54a5b59f30316ea44f Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:22:06 +0700 Subject: [PATCH 80/85] Remove more unused imports --- src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt | 1 - src/com/reco1l/osu/hud/elements/HUDComboCounter.kt | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt index 31ec8b2b5..10da39c1e 100644 --- a/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDAccuracyCounter.kt @@ -1,7 +1,6 @@ package com.reco1l.osu.hud.elements import com.reco1l.osu.hud.HUDElement -import com.reco1l.osu.hud.HUDElementSkinData import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.game.GameScene import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 diff --git a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt index 2cfbe2256..934824307 100644 --- a/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDComboCounter.kt @@ -1,9 +1,7 @@ package com.reco1l.osu.hud.elements import com.edlplan.framework.easing.* -import com.reco1l.andengine.Anchor import com.reco1l.andengine.modifier.OnModifierFinished -import com.reco1l.framework.math.Vec2 import com.reco1l.osu.hud.HUDElement import com.reco1l.osu.playfield.SpriteFont import ru.nsu.ccfit.zuev.osu.* From 9570b226558f1b90213f52ff9bd3f84607c323b2 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:31:22 +0700 Subject: [PATCH 81/85] Fix wrong comparison between `KClass` and `Class` --- src/com/reco1l/osu/hud/HUDSkinData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/reco1l/osu/hud/HUDSkinData.kt b/src/com/reco1l/osu/hud/HUDSkinData.kt index 6ea674bb0..38c9f394f 100644 --- a/src/com/reco1l/osu/hud/HUDSkinData.kt +++ b/src/com/reco1l/osu/hud/HUDSkinData.kt @@ -14,7 +14,7 @@ import kotlin.reflect.KClass data class HUDSkinData(val elements: List) { - fun hasElement(type: Class) = elements.any { it.type == type } + fun hasElement(type: Class) = elements.any { it.type.java == type } companion object { From 78bb4476b0a638f47e27dc4bd93f9c78662db908 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:46:59 +0700 Subject: [PATCH 82/85] Give separate names for PP, UR, and average offset counters --- src/com/reco1l/osu/hud/HUDElement.kt | 3 +-- src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt | 2 ++ src/com/reco1l/osu/hud/elements/HUDPPCounter.kt | 2 ++ src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/com/reco1l/osu/hud/HUDElement.kt b/src/com/reco1l/osu/hud/HUDElement.kt index ab0718680..86f3741b7 100644 --- a/src/com/reco1l/osu/hud/HUDElement.kt +++ b/src/com/reco1l/osu/hud/HUDElement.kt @@ -53,8 +53,7 @@ abstract class HUDElement : Container(), IGameplayEvents { /** * Returns the name of this element. */ - val name: String - get() = HUDElements[this::class].name.replace('_', ' ').capitalize() + open val name = HUDElements[this::class].name.replace('_', ' ').capitalize() /** * Indicates whether the element is selected. diff --git a/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt b/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt index 50bddd694..ea33fafd1 100644 --- a/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDAverageOffsetCounter.kt @@ -9,6 +9,8 @@ import kotlin.math.roundToInt class HUDAverageOffsetCounter : HUDElement() { + override val name = "Average offset counter" + private val text = ExtendedText().apply { font = ResourceManager.getInstance().getFont("smallFont") text = "Avg offset: 0ms" diff --git a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt index caa5c12c2..77bc5dea4 100644 --- a/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDPPCounter.kt @@ -11,6 +11,8 @@ import kotlin.math.roundToInt class HUDPPCounter : HUDElement() { + override val name = "PP counter" + private val sprite = SpriteFont(OsuSkin.get().scorePrefix) init { diff --git a/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt b/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt index d57ae6729..722cdff1d 100644 --- a/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt +++ b/src/com/reco1l/osu/hud/elements/HUDUnstableRateCounter.kt @@ -8,6 +8,8 @@ import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2 class HUDUnstableRateCounter : HUDElement() { + override val name = "Unstable rate counter" + private val text = ExtendedText().apply { font = ResourceManager.getInstance().getFont("smallFont") text = "UR: 0.00" From a663907b9066d523141845fa7d3d8695de3b2a40 Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:20:29 +0700 Subject: [PATCH 83/85] Fix gameplay restart in HUD editor mode potentially skipping too far ahead --- src/ru/nsu/ccfit/zuev/osu/game/GameScene.java | 7 +++++-- 1 file changed, 5 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 f96e8b4be..3ee2282a1 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -1635,9 +1635,12 @@ public void skip(boolean force) } ResourceManager.getInstance().getSound("menuhit").play(); - float difference = skipTime - elapsedTime; - elapsedTime = skipTime; + float difference = Math.max(0, skipTime - elapsedTime); + + // Skip time may be negative in forced skips, which will cause desynchronization between game time and + // audio time, so we cap it at 0. + elapsedTime = Math.max(0, skipTime); int seekTime = (int) Math.ceil(elapsedTime * 1000); int videoSeekTime = seekTime - (int) (videoOffset * 1000); From eaf349b4dbc2d9cca84c446bfd85cf700ea5bada Mon Sep 17 00:00:00 2001 From: Rian8337 <52914632+Rian8337@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:08:26 +0700 Subject: [PATCH 84/85] Move HUD editor strings to temporary locals --- res/values/strings_temporary_for_locals.xml | 16 ++++++++++++++++ res/xml/settings_gameplay.xml | 4 ++-- src/com/reco1l/osu/hud/GameplayHUD.kt | 20 +++++++++++--------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/res/values/strings_temporary_for_locals.xml b/res/values/strings_temporary_for_locals.xml index 36fd49163..110cccf31 100644 --- a/res/values/strings_temporary_for_locals.xml +++ b/res/values/strings_temporary_for_locals.xml @@ -22,4 +22,20 @@ Use snaking out sliders Enable sliders gradually snaking disappearing from one node + + + HUD Editor + Edit the HUD layout as you like + + HUD Editor + Exit and save changes + Discard changes + Reset to default + Cancel + + Saving changes... + Changes saved! + Changes discarded! + HUD set to default! + \ No newline at end of file diff --git a/res/xml/settings_gameplay.xml b/res/xml/settings_gameplay.xml index 5f117800f..bf78c0933 100644 --- a/res/xml/settings_gameplay.xml +++ b/res/xml/settings_gameplay.xml @@ -13,8 +13,8 @@ Date: Sat, 1 Mar 2025 12:20:33 +0700 Subject: [PATCH 85/85] Rename `updatePPCounter` to `updatePPValue` --- 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 3ee2282a1..5b8d14d2f 100644 --- a/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java +++ b/src/ru/nsu/ccfit/zuev/osu/game/GameScene.java @@ -1807,7 +1807,7 @@ private String registerHit(final int objectId, final int score, final boolean en gameover(); } if (objectId != -1) { - updatePPCounter(objectId); + updatePPValue(objectId); } return "hit0"; } @@ -1861,7 +1861,7 @@ private String registerHit(final int objectId, final int score, final boolean en } if (objectId != -1) { - updatePPCounter(objectId); + updatePPValue(objectId); } return scoreName; @@ -2776,7 +2776,7 @@ private void updateCounterTexts() { } } - private void updatePPCounter(int objectId) { + private void updatePPValue(int objectId) { if (Config.isHideInGameUI() || !isHUDEditorMode && !OsuSkin.get().getHUDSkinData().hasElement(HUDPPCounter.class)) { return; }