Merge branch 'rewrite/master' into feature/pico-results-perfect

This commit is contained in:
Cameron Taylor 2024-09-06 19:46:39 -04:00
commit b1e68c27fd
10 changed files with 285 additions and 62 deletions

2
assets

@ -1 +1 @@
Subproject commit 8e0d71c8f32ebd872f7f7411b3f7108083554de9 Subproject commit 7c913a9e209a4765c3bcb0c049a472086269846c

View file

@ -145,7 +145,7 @@
"name": "lime", "name": "lime",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "e0b2339e02fff91168789dbd1a0dd019ea3dda39", "ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
"url": "https://github.com/FunkinCrew/lime" "url": "https://github.com/FunkinCrew/lime"
}, },
{ {

View file

@ -225,7 +225,7 @@ class InitState extends FlxState
// -DRESULTS // -DRESULTS
FlxG.switchState(() -> new funkin.play.ResultState( FlxG.switchState(() -> new funkin.play.ResultState(
{ {
storyMode: false, storyMode: true,
title: "Cum Song Erect by Kawai Sprite", title: "Cum Song Erect by Kawai Sprite",
songId: "cum", songId: "cum",
characterId: "pico-playable", characterId: "pico-playable",

View file

@ -71,7 +71,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
public function hasNewCharacter():Bool public function hasNewCharacter():Bool
{ {
var characters = Save.instance.charactersSeen.clone(); var charactersSeen = Save.instance.charactersSeen.clone();
for (charId in listEntryIds()) for (charId in listEntryIds())
{ {
@ -79,7 +79,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
if (player == null) continue; if (player == null) continue;
if (!player.isUnlocked()) 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. // This character is unlocked but we haven't seen them in Freeplay yet.
return true; return true;
@ -89,6 +89,26 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
return false; return false;
} }
public function listNewCharacters():Array<String>
{
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. * Get the playable character associated with a given stage character.
* @param characterId The stage character ID. * @param characterId The stage character ID.

View file

@ -503,6 +503,12 @@ class PlayState extends MusicBeatSubState
*/ */
public var camGame:FlxCamera; 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. * 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 cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x
var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier. 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); camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95);
} }
@ -1458,6 +1464,13 @@ class PlayState extends MusicBeatSubState
super.destroy(); super.destroy();
} }
public override function initConsoleHelpers():Void
{
FlxG.console.registerFunction("debugUnbindCameraZoom", () -> {
debugUnbindCameraZoom = !debugUnbindCameraZoom;
});
};
/** /**
* Initializes the game and HUD cameras. * Initializes the game and HUD cameras.
*/ */

View file

@ -731,22 +731,40 @@ 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;
if (params.storyMode) if (params.storyMode)
{ {
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker))); if (PlayerRegistry.instance.hasNewCharacter())
{
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 else
{ {
var rigged:Bool = true; targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker));
if (rank > Scoring.calculateRank(params?.prevScoreData)) // if (rigged) }
}
else
{
if (rank > Scoring.calculateRank(params?.prevScoreData))
{ {
trace('THE RANK IS Higher.....'); 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", character: playerCharacterId ?? "bf",
@ -759,14 +777,13 @@ class ResultState extends MusicBeatSubState
playRankAnim: true playRankAnim: true
} }
} }
}));
}
}); });
} }
else else
{ {
trace('rank is lower...... and/or equal'); trace('rank is lower...... and/or equal');
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
{ {
{ {
fromResults: fromResults:
@ -778,9 +795,24 @@ class ResultState extends MusicBeatSubState
difficultyId: params.difficultyId difficultyId: params.difficultyId
} }
} }
}, sticker))); }, sticker));
} }
} }
if (shouldTween)
{
FlxTween.tween(rankBg, {alpha: 1}, 0.5,
{
ease: FlxEase.expoOut,
onComplete: function(_) {
FlxG.switchState(targetState);
}
});
}
else
{
FlxG.switchState(targetState);
}
} }
super.update(elapsed); super.update(elapsed);

View file

@ -49,7 +49,7 @@ class HealthIcon extends FunkinSprite
* this value allows you to set a relative scale for the icon. * this value allows you to set a relative scale for the icon.
* @default 1x scale = 150px width and height. * @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. * Apply the "bop" animation once every X steps.
@ -120,11 +120,15 @@ class HealthIcon extends FunkinSprite
{ {
super(0, 0); super(0, 0);
this.playerId = playerId; this.playerId = playerId;
this.size = new FlxCallbackPoint(onSetSize);
this.scrollFactor.set(); this.scrollFactor.set();
size.set(1.0, 1.0);
this.characterId = char; this.characterId = char;
}
initTargetSize(); function onSetSize(value:FlxPoint):Void
{
snapToTargetSize();
} }
function set_characterId(value:Null<String>):Null<String> function set_characterId(value:Null<String>):Null<String>
@ -243,6 +247,22 @@ class HealthIcon extends FunkinSprite
this.updateHitbox(); 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. * 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 function updateHealthIcon(health:Float):Void
{ {
// We want to efficiently handle animation playback // We want to efficiently handle animation playback

View file

@ -56,6 +56,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
Conductor.beatHit.add(this.beatHit); Conductor.beatHit.add(this.beatHit);
Conductor.stepHit.add(this.stepHit); Conductor.stepHit.add(this.stepHit);
initConsoleHelpers();
} }
public override function destroy():Void public override function destroy():Void
@ -79,6 +81,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
dispatchEvent(new UpdateScriptEvent(elapsed)); dispatchEvent(new UpdateScriptEvent(elapsed));
} }
public function initConsoleHelpers():Void {}
function reloadAssets() function reloadAssets()
{ {
PolymodHandler.forceReloadAssets(); PolymodHandler.forceReloadAssets();

View file

@ -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<PlayableCharacter>;
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);
});
}
}

View file

@ -344,8 +344,8 @@ class MainMenuState extends MusicBeatState
} }
} }
#if FEATURE_DEBUG_FUNCTIONS
// Open the debug menu, defaults to ` / ~ // 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) if (controls.DEBUG_MENU)
{ {
persistentUpdate = false; persistentUpdate = false;
@ -353,6 +353,18 @@ class MainMenuState extends MusicBeatState
FlxG.state.openSubState(new DebugMenuSubState()); 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) if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
{ {
FunkinSound.playOnce(Paths.sound('confirmMenu')); FunkinSound.playOnce(Paths.sound('confirmMenu'));