diff --git a/.github/actions/setup-haxe/action.yml b/.github/actions/setup-haxe/action.yml index 438f330a2..a6c21f6a9 100644 --- a/.github/actions/setup-haxe/action.yml +++ b/.github/actions/setup-haxe/action.yml @@ -82,7 +82,8 @@ runs: with: run: | git config --global --name-only --get-regexp 'url\.https\:\/\/x-access-token:.+@github\.com\/\.insteadOf' \ - | xargs git config --global --unset + | xargs -I {} git config --global --unset {} + git config -l --show-scope --show-origin git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/ post: git config --global --unset 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' diff --git a/assets b/assets index 49e375616..638a44fdd 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 49e375616e0c4f3fc66874ebe4262ac389d8c746 +Subproject commit 638a44fdd7635814db89e96bdfdc9a9be54b39c9 diff --git a/hmm.json b/hmm.json index eefaf86d5..50b81bb85 100644 --- a/hmm.json +++ b/hmm.json @@ -18,7 +18,7 @@ "name": "flixel", "type": "git", "dir": null, - "ref": "599f38eeb502a8ba6439784036c2cfdc7b485260", + "ref": "f2b090d6c608471e730b051c8ee22b8b378964b1", "url": "https://github.com/FunkinCrew/flixel" }, { @@ -46,7 +46,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "280d1a46ac60021d08bb18181631cbd6d061782c", + "ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412", "url": "https://github.com/Dot-Stuff/flxanimate" }, { @@ -115,6 +115,13 @@ "ref": "147294123f983e35f50a966741474438069a7a8f", "url": "https://github.com/FunkinCrew/hxcpp-debugger" }, + { + "name": "hxjsonast", + "type": "git", + "dir": null, + "ref": "20e72cc68c823496359775ac1f06500e67f189d5", + "url": "https://github.com/nadako/hxjsonast/" + }, { "name": "hxp", "type": "haxelib", @@ -145,7 +152,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "e0b2339e02fff91168789dbd1a0dd019ea3dda39", + "ref": "fe3368f611a84a19afc03011353945ae4da8fffd", "url": "https://github.com/FunkinCrew/lime" }, { @@ -205,4 +212,4 @@ "url": "https://github.com/fponticelli/thx.semver" } ] -} \ No newline at end of file +} diff --git a/project.hxp b/project.hxp index 0e090a4c9..1193a9cd4 100644 --- a/project.hxp +++ b/project.hxp @@ -469,7 +469,7 @@ class Project extends HXProject { // Should be true only on web builds. // Enabling embedding and preloading is required to preload assets properly. EMBED_ASSETS.apply(this, isWeb()); - PRELOAD_ALL.apply(this, true); + PRELOAD_ALL.apply(this, !isWeb()); // Should be true except on MacOS. // File drop doesn't work there. diff --git a/source/Main.hx b/source/Main.hx index dc462ee5c..63e2b5878 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -66,6 +66,18 @@ class Main extends Sprite function init(?event:Event):Void { + #if web + untyped js.Syntax.code(" + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = 0; + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }"); + #end + if (hasEventListener(Event.ADDED_TO_STAGE)) { removeEventListener(Event.ADDED_TO_STAGE, init); diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index a203a8d77..f71de00f4 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -225,7 +225,7 @@ class InitState extends FlxState // -DRESULTS FlxG.switchState(() -> new funkin.play.ResultState( { - storyMode: false, + storyMode: true, title: "Cum Song Erect by Kawai Sprite", songId: "cum", characterId: "pico-playable", diff --git a/source/funkin/data/freeplay/player/PlayerRegistry.hx b/source/funkin/data/freeplay/player/PlayerRegistry.hx index c0a15ed1c..76b1c25c1 100644 --- a/source/funkin/data/freeplay/player/PlayerRegistry.hx +++ b/source/funkin/data/freeplay/player/PlayerRegistry.hx @@ -71,7 +71,7 @@ class PlayerRegistry extends BaseRegistry public function hasNewCharacter():Bool { - var characters = Save.instance.charactersSeen.clone(); + var charactersSeen = Save.instance.charactersSeen.clone(); for (charId in listEntryIds()) { @@ -79,7 +79,7 @@ class PlayerRegistry extends BaseRegistry if (player == null) continue; if (!player.isUnlocked()) continue; - if (characters.contains(charId)) continue; + if (charactersSeen.contains(charId)) continue; // This character is unlocked but we haven't seen them in Freeplay yet. return true; @@ -89,6 +89,26 @@ class PlayerRegistry extends BaseRegistry return false; } + public function listNewCharacters():Array + { + var charactersSeen = Save.instance.charactersSeen.clone(); + var result = []; + + for (charId in listEntryIds()) + { + var player = fetchEntry(charId); + if (player == null) continue; + + if (!player.isUnlocked()) continue; + if (charactersSeen.contains(charId)) continue; + + // This character is unlocked but we haven't seen them in Freeplay yet. + result.push(charId); + } + + return result; + } + /** * Get the playable character associated with a given stage character. * @param characterId The stage character ID. diff --git a/source/funkin/data/stage/StageRegistry.hx b/source/funkin/data/stage/StageRegistry.hx index 3d3779bce..87113ef05 100644 --- a/source/funkin/data/stage/StageRegistry.hx +++ b/source/funkin/data/stage/StageRegistry.hx @@ -93,8 +93,8 @@ class StageRegistry extends BaseRegistry public function listBaseGameStageIds():Array { return [ - "mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallEvil", "school", - "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyStreetsErect", "phillyBlazin", + "mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallXmasErect", "mallEvil", + "school", "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyStreetsErect", "phillyBlazin", ]; } diff --git a/source/funkin/graphics/shaders/TextureSwap.hx b/source/funkin/graphics/shaders/TextureSwap.hx new file mode 100644 index 000000000..65de87ea3 --- /dev/null +++ b/source/funkin/graphics/shaders/TextureSwap.hx @@ -0,0 +1,48 @@ +package funkin.graphics.shaders; + +import flixel.system.FlxAssets.FlxShader; +import flixel.util.FlxColor; +import openfl.display.BitmapData; + +class TextureSwap extends FlxShader +{ + public var swappedImage(default, set):BitmapData; + public var amount(default, set):Float; + + function set_swappedImage(_bitmapData:BitmapData):BitmapData + { + image.input = _bitmapData; + + return _bitmapData; + } + + function set_amount(val:Float):Float + { + fadeAmount.value = [val]; + + return val; + } + + @:glFragmentSource(' + #pragma header + + uniform sampler2D image; + uniform float fadeAmount; + + void main() + { + vec4 tex = flixel_texture2D(bitmap, openfl_TextureCoordv); + vec4 tex2 = flixel_texture2D(image, openfl_TextureCoordv); + + vec4 finalColor = mix(tex, vec4(tex2.rgb, tex.a), fadeAmount); + + gl_FragColor = finalColor; + } + ') + public function new() + { + super(); + + this.amount = 1; + } +} diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 6eb954c0f..f6a7148f8 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -17,6 +17,7 @@ import funkin.ui.story.StoryMenuState; import funkin.util.MathUtil; import openfl.utils.Assets; import funkin.effects.RetroCameraFade; +import flixel.math.FlxPoint; /** * A substate which renders over the PlayState when the player dies. @@ -168,8 +169,8 @@ class GameOverSubState extends MusicBeatSubState // Assign a camera follow point to the boyfriend's position. cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); - cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; - cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; + cameraFollowPoint.x = getMidPointOld(boyfriend).x; + cameraFollowPoint.y = getMidPointOld(boyfriend).y; var offsets:Array = boyfriend.getDeathCameraOffsets(); cameraFollowPoint.x += offsets[0]; cameraFollowPoint.y += offsets[1]; @@ -180,6 +181,21 @@ class GameOverSubState extends MusicBeatSubState targetCameraZoom = (PlayState?.instance?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom(); } + /** + * FlxSprite.getMidpoint(); calculations changed in this git commit + * https://github.com/HaxeFlixel/flixel/commit/1553b5af0871462fcefedc091b7885437d6c36d2 + * https://github.com/HaxeFlixel/flixel/pull/3125 + * + * So we use this to do the old math that gets the midpoint of our graphics + * Luckily, we don't use getGraphicMidpoint() much in the code, so it's fine being in GameoverSubState here. + * @return FlxPoint + */ + function getMidPointOld(spr:FlxSprite, ?point:FlxPoint):FlxPoint + { + if (point == null) point = FlxPoint.get(); + return point.set(spr.x + spr.frameWidth * 0.5 * spr.scale.x, spr.y + spr.frameHeight * 0.5 * spr.scale.y); + } + /** * Forcibly reset the camera zoom level to that of the current stage. * This prevents camera zoom events from adversely affecting the game over state. diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 414c833ff..0b2b8846d 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -503,6 +503,12 @@ class PlayState extends MusicBeatSubState */ public var camGame:FlxCamera; + /** + * Simple helper debug variable, to be able to move the camera around for debug purposes + * without worrying about the camera tweening back to the follow point. + */ + public var debugUnbindCameraZoom:Bool = false; + /** * The camera which contains, and controls visibility of, a video cutscene, dialogue, pause menu and sticker transition. */ @@ -992,7 +998,7 @@ class PlayState extends MusicBeatSubState { cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier. - FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera. + if (!debugUnbindCameraZoom) FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera. camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95); } @@ -1458,6 +1464,13 @@ class PlayState extends MusicBeatSubState super.destroy(); } + public override function initConsoleHelpers():Void + { + FlxG.console.registerFunction("debugUnbindCameraZoom", () -> { + debugUnbindCameraZoom = !debugUnbindCameraZoom; + }); + }; + /** * Initializes the game and HUD cameras. */ @@ -2224,10 +2237,10 @@ class PlayState extends MusicBeatSubState // Skip handling the miss in botplay! if (!isBotPlayMode) { - // Judge the miss. - // NOTE: This is what handles the scoring. - trace('Missed note! ${note.noteData}'); - onNoteMiss(note, event.playSound, event.healthChange); + // Judge the miss. + // NOTE: This is what handles the scoring. + trace('Missed note! ${note.noteData}'); + onNoteMiss(note, event.playSound, event.healthChange); } note.handledMiss = true; @@ -2344,31 +2357,31 @@ class PlayState extends MusicBeatSubState playerStrumline.playPress(input.noteDirection); trace('PENALTY Score: ${songScore}'); } - else if (notesInDirection.length == 0) - { - // Press a key with no penalty. + else if (notesInDirection.length == 0) + { + // Press a key with no penalty. - // Play the strumline animation. - playerStrumline.playPress(input.noteDirection); - trace('NO PENALTY Score: ${songScore}'); - } - else - { - // Choose the first note, deprioritizing low priority notes. - var targetNote:Null = notesInDirection.find((note) -> !note.lowPriority); - if (targetNote == null) targetNote = notesInDirection[0]; - if (targetNote == null) continue; + // Play the strumline animation. + playerStrumline.playPress(input.noteDirection); + trace('NO PENALTY Score: ${songScore}'); + } + else + { + // Choose the first note, deprioritizing low priority notes. + var targetNote:Null = notesInDirection.find((note) -> !note.lowPriority); + if (targetNote == null) targetNote = notesInDirection[0]; + if (targetNote == null) continue; - // Judge and hit the note. - trace('Hit note! ${targetNote.noteData}'); - goodNoteHit(targetNote, input); - trace('Score: ${songScore}'); + // Judge and hit the note. + trace('Hit note! ${targetNote.noteData}'); + goodNoteHit(targetNote, input); + trace('Score: ${songScore}'); - notesInDirection.remove(targetNote); + notesInDirection.remove(targetNote); - // Play the strumline animation. - playerStrumline.playConfirm(input.noteDirection); - } + // Play the strumline animation. + playerStrumline.playConfirm(input.noteDirection); + } } while (inputReleaseQueue.length > 0) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index ec6069ad4..e7087e340 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -4,6 +4,8 @@ import funkin.util.MathUtil; import funkin.ui.story.StoryMenuState; import funkin.graphics.adobeanimate.FlxAtlasSprite; import flixel.FlxSprite; +import flixel.FlxState; +import flixel.FlxSubState; import funkin.graphics.FunkinSprite; import flixel.effects.FlxFlicker; import flixel.graphics.frames.FlxBitmapFont; @@ -731,54 +733,93 @@ class ResultState extends MusicBeatSubState } }); } + + // Determining the target state(s) to go to. + // Default to main menu because that's better than `null`. + var targetState:flixel.FlxState = new funkin.ui.mainmenu.MainMenuState(); + var shouldTween = false; + var shouldUseSubstate = false; + if (params.storyMode) { - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker))); + if (PlayerRegistry.instance.hasNewCharacter()) + { + // New character, display the notif. + targetState = new StoryMenuState(null); + + var newCharacters = PlayerRegistry.instance.listNewCharacters(); + + for (charId in newCharacters) + { + shouldTween = true; + // This works recursively, ehe! + targetState = new funkin.ui.charSelect.CharacterUnlockState(charId, targetState); + } + } + else + { + // No new characters. + shouldTween = false; + shouldUseSubstate = true; + targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)); + } } else { - var rigged:Bool = true; - if (rank > Scoring.calculateRank(params?.prevScoreData)) // if (rigged) + if (rank > Scoring.calculateRank(params?.prevScoreData)) { trace('THE RANK IS Higher.....'); - FlxTween.tween(rankBg, {alpha: 1}, 0.5, + shouldTween = true; + targetState = FreeplayState.build( { - ease: FlxEase.expoOut, - onComplete: function(_) { - FlxG.switchState(FreeplayState.build( + { + character: playerCharacterId ?? "bf", + fromResults: { - { - character: playerCharacterId ?? "bf", - fromResults: - { - oldRank: Scoring.calculateRank(params?.prevScoreData), - newRank: rank, - songId: params.songId, - difficultyId: params.difficultyId, - playRankAnim: true - } - } - })); + oldRank: Scoring.calculateRank(params?.prevScoreData), + newRank: rank, + songId: params.songId, + difficultyId: params.difficultyId, + playRankAnim: true + } } }); } else { - trace('rank is lower...... and/or equal'); - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build( - { + shouldTween = false; + shouldUseSubstate = true; + targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)); + } + } + + if (shouldTween) + { + FlxTween.tween(rankBg, {alpha: 1}, 0.5, + { + ease: FlxEase.expoOut, + onComplete: function(_) { + if (shouldUseSubstate && targetState is FlxSubState) { - fromResults: - { - oldRank: null, - playRankAnim: false, - newRank: rank, - songId: params.songId, - difficultyId: params.difficultyId - } + openSubState(cast targetState); } - }, sticker))); + else + { + FlxG.switchState(targetState); + } + } + }); + } + else + { + if (shouldUseSubstate && targetState is FlxSubState) + { + openSubState(cast targetState); + } + else + { + FlxG.switchState(targetState); } } } diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 28b2dbee2..bce7f0d98 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -376,7 +376,7 @@ class BaseCharacter extends Bopper { if (isAnimationFinished()) { - trace('Not playing hold (${getCurrentAnimation()}) (${isAnimationFinished()}, ${getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)}, ${hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX)})'); + // trace('Not playing hold (${getCurrentAnimation()}) (${isAnimationFinished()}, ${getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)}, ${hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX)})'); } } diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index 0e24d73fb..c11850b2a 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -49,7 +49,7 @@ class HealthIcon extends FunkinSprite * this value allows you to set a relative scale for the icon. * @default 1x scale = 150px width and height. */ - public var size:FlxPoint = new FlxPoint(1, 1); + public var size:FlxPoint; /** * Apply the "bop" animation once every X steps. @@ -120,11 +120,15 @@ class HealthIcon extends FunkinSprite { super(0, 0); this.playerId = playerId; + this.size = new FlxCallbackPoint(onSetSize); this.scrollFactor.set(); - + size.set(1.0, 1.0); this.characterId = char; + } - initTargetSize(); + function onSetSize(value:FlxPoint):Void + { + snapToTargetSize(); } function set_characterId(value:Null):Null @@ -243,6 +247,22 @@ class HealthIcon extends FunkinSprite this.updateHitbox(); } + /* + * Immediately snap the health icon to its target size without lerping. + */ + public function snapToTargetSize():Void + { + if (this.width > this.height) + { + setGraphicSize(Std.int(HEALTH_ICON_SIZE * this.size.x), 0); + } + else + { + setGraphicSize(0, Std.int(HEALTH_ICON_SIZE * this.size.y)); + } + updateHitbox(); + } + /** * Update the position (and status) of the health icon. */ @@ -301,12 +321,6 @@ class HealthIcon extends FunkinSprite } } - inline function initTargetSize():Void - { - setGraphicSize(HEALTH_ICON_SIZE); - updateHitbox(); - } - function updateHealthIcon(health:Float):Void { // We want to efficiently handle animation playback diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 5c40b37bc..02cebeb45 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -56,6 +56,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler Conductor.beatHit.add(this.beatHit); Conductor.stepHit.add(this.stepHit); + + initConsoleHelpers(); } public override function destroy():Void @@ -79,6 +81,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler dispatchEvent(new UpdateScriptEvent(elapsed)); } + public function initConsoleHelpers():Void {} + function reloadAssets() { PolymodHandler.forceReloadAssets(); diff --git a/source/funkin/ui/charSelect/CharacterUnlockState.hx b/source/funkin/ui/charSelect/CharacterUnlockState.hx new file mode 100644 index 000000000..b32a35145 --- /dev/null +++ b/source/funkin/ui/charSelect/CharacterUnlockState.hx @@ -0,0 +1,128 @@ +package funkin.ui.charSelect; + +import flixel.FlxSprite; +import flixel.FlxState; +import flixel.group.FlxSpriteGroup; +import flixel.text.FlxText; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import funkin.play.character.CharacterData; +import funkin.play.character.CharacterData.CharacterDataParser; +import funkin.play.components.HealthIcon; +import funkin.ui.freeplay.charselect.PlayableCharacter; +import funkin.data.freeplay.player.PlayerData; +import funkin.data.freeplay.player.PlayerRegistry; +import funkin.ui.mainmenu.MainMenuState; + +using flixel.util.FlxSpriteUtil; + +/** + * When you want the player to unlock a character, call `CharacterUnlockState.unlock(characterName)`. + * It handles both the act of unlocking the character and displaying the dialog. + */ +class CharacterUnlockState extends MusicBeatState +{ + public var targetCharacterId:String = ""; + public var targetCharacterData:Null; + + var nextState:FlxState; + + static final DIALOG_BG_COLOR:FlxColor = 0xFF000000; // Iconic + static final DIALOG_COLOR:FlxColor = 0xFF4344F6; // Iconic + static final DIALOG_FONT_COLOR:FlxColor = 0xFFFFFFFF; // Iconic + + var busy:Bool = false; + + public function new(targetPlayableCharacter:String, ?nextState:FlxState) + { + super(); + + this.targetCharacterId = targetPlayableCharacter; + this.targetCharacterData = PlayerRegistry.instance.fetchEntry(targetCharacterId); + this.nextState = nextState == null ? new MainMenuState() : nextState; + } + + override function create():Void + { + super.create(); + + handleMusic(); + + bgColor = DIALOG_BG_COLOR; + + var dialogContainer:FlxSpriteGroup = new FlxSpriteGroup(); + add(dialogContainer); + + // Build the graphic for the text... + var charName:String = targetCharacterData != null ? targetCharacterData.getName() : targetCharacterId.toTitleCase(); + // var dialogText:FlxText = new FlxText(0, 0, 0, 'You can now play as $charName.\n\nCheck it out in Freeplay!'); + var dialogText:FlxText = new FlxText(0, 0, 0, 'You can now play as $charName.'); + dialogText.setFormat("VCR OSD Mono", 32, DIALOG_FONT_COLOR, LEFT); + + // THEN we can size the dialog to match... + var dialogBG:FlxSprite = new FlxSprite(0, 0); + dialogBG.makeGraphic(Std.int(dialogText.width + 32), Std.int(dialogText.height + 32), FlxColor.TRANSPARENT); + dialogBG.drawRoundRect(0, 0, dialogBG.width, dialogBG.height, 16, 16, DIALOG_COLOR); + dialogContainer.add(dialogBG); + + dialogBG.screenCenter(XY); + + // THEN we can position the text inside that. + dialogText.x = dialogBG.x + 16; + dialogText.y = dialogBG.y + 16; + dialogContainer.add(dialogText); + + // HealthIcon handles getting the right frames for us, + // but it has a bunch of overhead in it that makes it gross to work with outside the health bar. + var healthIconCharacterId = targetCharacterData.getOwnedCharacterIds()[0]; + var baseCharacter = CharacterDataParser.fetchCharacter(healthIconCharacterId); + var healthIcon:HealthIcon = new HealthIcon(healthIconCharacterId); + @:privateAccess + healthIcon.configure(baseCharacter._data.healthIcon); + healthIcon.autoUpdate = false; + healthIcon.bopEvery = 0; // You can increase this number later once the animation is done. + healthIcon.size.set(0.5, 0.5); + healthIcon.x = dialogBG.x + 390; + healthIcon.y = dialogBG.y + 6; + healthIcon.flipX = true; + healthIcon.snapToTargetSize(); + dialogContainer.add(healthIcon); + + dialogContainer.scale.set(0, 0); + FlxTween.num(0.0, 1.0, 0.75, + { + ease: FlxEase.elasticOut, + }, function(curScale) { + dialogContainer.scale.set(curScale, curScale); + healthIcon.size.set(0.5 * curScale, 0.5 * curScale); + }); + + // performUnlock(); + } + + function handleMusic():Void + { + FlxG.sound.music?.stop(); + FlxG.sound.play(Paths.sound('confirmMenu')); + } + + override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (controls.ACCEPT || controls.BACK && !busy) + { + busy = true; + startClose(); + } + } + + function startClose():Void + { + // Fade to black, then switch state. + FlxG.camera.fade(FlxColor.BLACK, 0.75, false, () -> { + FlxG.switchState(nextState); + }); + } +} diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 20d39183b..811e08e5d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5707,7 +5707,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState PlayStatePlaylist.campaignId = 'week3'; case 'limoRide' | 'limoRideErect': PlayStatePlaylist.campaignId = 'week4'; - case 'mallXmas' | 'mallEvil': + case 'mallXmas' | 'mallXmasErect' | 'mallEvil': PlayStatePlaylist.campaignId = 'week5'; case 'school' | 'schoolEvil': PlayStatePlaylist.campaignId = 'week6'; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index e421efd8a..a908e54c6 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -253,6 +253,8 @@ class FreeplayState extends MusicBeatSubState switch (currentCharacterId) { + case(PlayerRegistry.instance.hasNewCharacter()) => true: + backingCard = new NewCharacterCard(currentCharacter); case 'bf': backingCard = new BoyfriendCard(currentCharacter); case 'pico': @@ -347,6 +349,7 @@ class FreeplayState extends MusicBeatSubState var displayedVariations = song.getVariationsByCharacter(currentCharacter); trace('Displayed Variations (${songId}): $displayedVariations'); var availableDifficultiesForSong:Array = song.listSuffixedDifficulties(displayedVariations, false, false); + var unsuffixedDifficulties = song.listDifficulties(displayedVariations, false, false); trace('Available Difficulties: $availableDifficultiesForSong'); if (availableDifficultiesForSong.length == 0) continue; @@ -355,6 +358,10 @@ class FreeplayState extends MusicBeatSubState { diffIdsTotal.pushUnique(difficulty); } + for (difficulty in unsuffixedDifficulties) + { + diffIdsTotal.pushUnique(difficulty); + } } } @@ -1985,22 +1992,12 @@ class FreeplayState extends MusicBeatSubState return; } - var baseInstrumentalId:String = targetSong?.getBaseInstrumentalId(targetDifficultyId, targetDifficulty.variation ?? Constants.DEFAULT_VARIATION) ?? ''; - var altInstrumentalIds:Array = targetSong?.listAltInstrumentalIds(targetDifficultyId, - targetDifficulty.variation ?? Constants.DEFAULT_VARIATION) ?? []; - - var targetInstId:String = baseInstrumentalId; - - // TODO: Make this a UI element. - #if FEATURE_DEBUG_FUNCTIONS - if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL) + if (targetInstId == null) { - targetInstId = altInstrumentalIds[0]; + var baseInstrumentalId:String = targetSong?.getBaseInstrumentalId(targetDifficultyId, targetDifficulty.variation ?? Constants.DEFAULT_VARIATION) ?? ''; + targetInstId = baseInstrumentalId; } - if (targetInstId == null) targetInstId = baseInstrumentalId; - #end - // Visual and audio effects. FunkinSound.playOnce(Paths.sound('confirmMenu')); if (dj != null) dj.confirm(); @@ -2380,7 +2377,7 @@ class FreeplaySongData this.scoringRank = Save.instance.getSongRank(songId, suffixedDifficulty); - this.isNew = song.isSongNew(currentDifficulty); + this.isNew = song.isSongNew(suffixedDifficulty); } } @@ -2422,7 +2419,11 @@ class DifficultySprite extends FlxSprite // Remove the last suffix of the difficulty id until we find an asset or there are no more suffixes. var assetDiffIdParts:Array = assetDiffId.split('-'); assetDiffIdParts.pop(); - if (assetDiffIdParts.length == 0) break; + if (assetDiffIdParts.length == 0) + { + trace('Could not find difficulty asset: freeplay/freeplay${diffId} (from ${diffId})'); + return; + }; assetDiffId = assetDiffIdParts.join('-'); } @@ -2436,6 +2437,7 @@ class DifficultySprite extends FlxSprite else { this.loadGraphic(Paths.image('freeplay/freeplay' + assetDiffId)); + trace('Loaded difficulty asset: freeplay/freeplay${assetDiffId} (from ${diffId})'); } } } diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index d2a9f046a..19dc0d687 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -344,8 +344,8 @@ class MainMenuState extends MusicBeatState } } - #if FEATURE_DEBUG_FUNCTIONS // Open the debug menu, defaults to ` / ~ + // This includes stuff like the Chart Editor, so it should be present on all builds. if (controls.DEBUG_MENU) { persistentUpdate = false; @@ -353,6 +353,18 @@ class MainMenuState extends MusicBeatState FlxG.state.openSubState(new DebugMenuSubState()); } + #if FEATURE_DEBUG_FUNCTIONS + // Ctrl+Alt+Shift+P = Character Unlock screen + // Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock + // Ctrl+Alt+Shift+L = Revoke requirements for Pico Unlock + // Ctrl+Alt+Shift+R = Score/Rank conflict test + // Ctrl+Alt+Shift+E = Dump save data + + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P) + { + FlxG.switchState(() -> new funkin.ui.charSelect.CharacterUnlockState('pico')); + } + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W) { FunkinSound.playOnce(Paths.sound('confirmMenu')); diff --git a/source/funkin/ui/options/FunkinSoundTray.hx b/source/funkin/ui/options/FunkinSoundTray.hx index 792e38fc4..170ad8497 100644 --- a/source/funkin/ui/options/FunkinSoundTray.hx +++ b/source/funkin/ui/options/FunkinSoundTray.hx @@ -120,7 +120,7 @@ class FunkinSoundTray extends FlxSoundTray lerpYPos = 10; visible = true; active = true; - var globalVolume:Int = Math.round(FlxG.sound.volume * 10); + var globalVolume:Int = Math.round(FlxG.sound.logToLinear(FlxG.sound.volume) * 10); if (FlxG.sound.muted) { diff --git a/source/funkin/util/FlxColorUtil.hx b/source/funkin/util/FlxColorUtil.hx new file mode 100644 index 000000000..429d536d8 --- /dev/null +++ b/source/funkin/util/FlxColorUtil.hx @@ -0,0 +1,22 @@ +package funkin.util; + +import flixel.util.FlxColor; + +/** + * Non inline FlxColor functions for use in hscript files + */ +class FlxColorUtil +{ + /** + * Get an interpolated color based on two different colors. + * + * @param Color1 The first color + * @param Color2 The second color + * @param Factor Value from 0 to 1 representing how much to shift Color1 toward Color2 + * @return The interpolated color + */ + public static function interpolate(Color1:FlxColor, Color2:FlxColor, Factor:Float = 0.5):FlxColor + { + return FlxColor.interpolate(Color1, Color2, Factor); + } +}